1 Setup

In this notebook we use the following R packages: tidyverse and ggplot2. In addition we use the multiplot functionality provided by Cookbook for R. In Python we use fastai, numpy and pandas. Full code for training and inference available at GitHub. All code for statistical analysis is included in this document.

2 Introduction

The success of deep learning models has typically been measured in terms of their predictive power, but they have lacked a principled way of expressing uncertainty about these predictions.

In my master’s thesis we apply recent insights connecting dropout neural networks to Bayesian approximate variational inference (VI). VI is technique for approximating intractable integrals that arise when modelling the posterior distribution in Bayesian inference and machine learning. Gal et. al. [1, 2, 3] have shown that the posterior distribution of a NN can be approximated by leaving dropout on at test time and sampling multiple predictions. This amounts to drawing Monte Carlo (MC) samples from the posterior (a brief description of this process is listed in section 1.1). The Bayesian framework allows us to obtain principled uncertainty estimates when making predictions in these so called MC dropout NNs.

The results of [1,2,3] seem particularly promising when applied to healthcare. In fact, a recent paper published in Nature [4] applies MC dropout to capture uncertainty estimates when predicting the presence of diabetic retinopathy in patients. This paper demonstrates how uncertainty estimates can provide useful information to the clinician tasked with interpreting the results of medical images. The key idea is that this human-machine interaction will lead to overall better results than either could produce individually.

In this notebook I will first briefly introduce the general idea behind MC dropout. Then we will apply MC dropout in a classification task based on the collection of labelled images in the CIFAR-10 data set.

2.1 Background

A neural network is made up of many neurons which are organized in layers. Neurons are often called nodes or units, depending on your choice of machine learning literature. Henceforth we will refer to them as units.

In a typical neural network there are many, many units. As a consequence the number of parameters greatly exceeds the number of data points, dramatically increasing the risk of overfitting (i.e. there are many settings of weights which will cause the network to fit the data almost perfectly).

Dropout is a common stochastic regularisation technique [5] that is used to prevent overfitting in neural networks. The term dropout refers to randomly “dropping out” nonoutput units, temporarily removing all connections to the rest of the network. The main idea is that if the presence of other units is unreliable, each unit is forced to learn how to be useful on its own. At test time all units are activated, and the weights of the network are scaled by the dropout rate in order to match the expected output during training.

Recent work by Gal et. al. [1, 2, 3] casts dropout neural networks as approximate Bayesian inference. Their results show that the predictive posterior distribution of a neural network can be approximated by leaving dropout on at test time.

Consider a classification setting, such as in CIFAR-10. Essentially, what happens when applying MC dropout is the following:

  • An image is fed forward through the network \(T\) times (we use \(T=100\) as recommended in [4]). Each time the image is fed through is called a stochastic forward pass.
  • For each stochastic forward pass, a slightly different network is making predictions because dropout has randomly switched off units.
  • As a result each stochastic forward pass returns 100 slightly different vectors of class predictions.
  • To make a prediction we average the 100 vectors. The class corresponding to the largest element in the resulting vector is our final prediction.
  • Finally we calculate the standard deviation in class predictions over all forward passes. This is our estimated uncertainty.

Mathematically, model uncertainy is approximated the empirical standard deviation of the predictions for class \(k\), i.e. \[\hat{\sigma}_k = \sqrt{\frac{\sum_{t=1}^T{[p_t(k|X,w) - \hat{\mu}_k}]^2}{T-1}}\] where \[\hat{\mu}_k = \frac{1}{T}\sum_{t=1}^T p_t(k|X,w)\] is the averaged softmax outputs of the predicted class.

Gal et. al. [3] show that the above amounts to drawing Monte Carlo samples from the predictive posterior. Their work demonstrates that applying dropout is effectively the same as defining a Bayesian neural network with a Bernoulli approximating prior over the parameters. Gal has written an excellent blog post that introduces the work and the derivation.

2.2 Problem statement

The derivation in Gal et. al. [2] is based on the observation that a shallow neural network with infinitely many weights converges to a Gaussian process (GP) with a specific covariance function. A GP is a non-parametric, probabilistic machine learning method. The idea is that we place a prior distribution over the function space, and by observing new data points we can figure out which function is most likely to have generated the observed data. A GP is fully specified by its mean and covariance functions, and allows us to obtain uncertainty estimates [1]. The extension of this to dropout neural networks is the main result of Gal et. al. [2].

In [1] however, Gal et. al. extend this idea further to include priors over the weights of the convolutional layers in a convolutional neural network (CNN). A CNN is a specialized type of neural network, used primarily and very effectively for image analysis. The authors briefly state that the GP interpretation is lost when the model is extended to CNNs, but that these networks still can be “modelled as Bayesian”.

As far as we are aware, there have been no efforts to determine the correlation between the approximated uncertainty and a network’s ability to predict correctly. Moreover, the work done so far seems to focus on the uncertainty associated with the predicted class. We will examine if there is any additional information to be gained from establishing a connection between the prediction and the runner-up prediction. In other words, our problem statement is:

Are the uncertainty approximations obtained by applying Monte Carlo dropout to convolutional neural networks a reasonable measure of model uncertainty?

2.3 Experimental setup

State-of-the-art architectures such as ResNet and DenseNet are very powerful, but they are also complicated and their inner workings are quite convoluted. We are primarily interested in examining the correlation between uncertainty estimation and predictive capabilities. It is arguably better to use a simple network architecture to illustrate the idea of MC dropout. In our approach we use LeNet-5. Click on the tabs for implementation details. Full code is available on GitHub.

2.3.1 Model

LeNet-5 was a pioneering 7-layer convolutional neural network originally developed by Yann LeCunn in 1998 for handwritten digit recognition. It is hopelessly primitive compared to contemporary architectures, but still captures the gist of what a convolutional network is while remaining simple enough to allow us to understand every building block of the network. The following chunk shows the model architecture:

class lenet_all(nn.Module):
    def __init__(self, conv_size=conv_size, pool_size=2, drop_rate=p):
        super().__init__()
        self.drop_rate = drop_rate
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=192, kernel_size=conv_size)
        self.dropmc1 = DropoutMC(p)
        self.pool1 = nn.MaxPool2d(kernel_size=pool_size, stride=2)
        self.conv2 = nn.Conv2d(in_channels=192, out_channels=192, kernel_size=conv_size, padding=2)
        self.dropmc2 = DropoutMC(p)
        self.pool2 = nn.MaxPool2d(kernel_size=pool_size, stride=2)
        self.dense1 = nn.Linear(in_features=7*7*192, out_features=1000)
        self.dropmc3 = DropoutMC(p)
        self.dense2 = nn.Linear(in_features=1000, out_features=10)
        
    def forward(self, x):
        x = self.dropmc1(self.conv1(x))
        x = self.pool1(x)
        x = self.dropmc2(self.conv2(x))
        x = self.pool2(x)
        x = x.view(x.size(0), -1)
        x = self.dropmc3(F.relu(self.dense1(x)))       
        x = self.dense2(x)
        
        return x


2.3.2 MC dropout layer

Our specification of LeNet-5 differs from the orginial in one crucial way: We use Monte Carlo dropout layers. MC dropout is not a feature that is implemented in PyTorch, and we must therefore implemenet one ourselves. Fortunately, this amounts to a simple adjustment of existing code. We modify the Dropout class to take an additional argument called dropoutMC with default value set to True:

class DropoutMC(nn.Module):
    r"""
    Modified version of Dropout from torch/nn/modules/dropout.py
    Args:
        p: probability of an element to be zeroed. Default: 0.5
        dropoutMC: If set to ``True``, dropout is turned on at test time. Default: ``True`
        inplace: If set to ``True``, will do this operation in-place. Default: ``False``
    Shape:
        - Input: `Any`. Input can be of any shape
        - Output: `Same`. Output is of the same shape as input
    Examples::
        >>> m = nn.Dropout(p=0.2)
        >>> input = autograd.Variable(torch.randn(20, 16))
        >>> output = m(input)
    .. _Improving neural networks by preventing co-adaptation of feature
        detectors: https://arxiv.org/abs/1207.0580
    """

    def __init__(self, p=0.5, dropoutMC=True, inplace=False):
        super(DropoutMC, self).__init__()
        if p < 0 or p > 1:
            raise ValueError("dropout probability has to be between 0 and 1, "
                             "but got {}".format(p))
        self.p = p
        self.dropoutMC = dropoutMC
        self.inplace = inplace

    def forward(self, input):
        return F.dropout(input, self.p, self.dropoutMC, self.inplace)

    def __repr__(self):
        inplace_str = ', inplace' if self.inplace else ''
        return self.__class__.__name__ + '(' \
            + 'p=' + str(self.p) \
            + inplace_str + ')'


2.3.3 Inference

We need to define a function that performs inference over out input. The following chunk contains the relevant code for the sampling procedure described in section 1.1. The function inference stores all the relevant statistics and softmax distributions in a dictionary named output. The results are then turned into a pandas dataframe and some very basic feature engineering is performed. Finally, the data is prepared for statistical analysis in R.

def inference(learner, data, T=100):
    ''' Function that gathers all relevant numerical results from MC dropout over T iterations.
        
        Arguments:
        learner, fastai learner object
        data, fastai dataloader
        T, number of stochastic forward passes
    '''
    # Get images, labels and filenames
    imgs, labels = next(iter(data.val_dl))
    fnames = data.val_ds.fnames

    # Empty dictionary to store all output
    output = {}

    # Empty array to store results in
    results = np.empty((T, num_classes))

    # iterator index to keep in dictionary
    k=0

    for (img, label, fname) in list(zip(imgs, labels, fnames)):

        for i in range(T):
            prediction = learner.predict_array(img[None])
            results[i] = prediction

        probs = to_np(F.softmax(V(results)))
        probs_mean = np.mean(probs, axis=0)
        pred_std = np.std(probs, axis=0)

        prediction = probs_mean.argmax()
        uncertainty = pred_std[prediction]

        correct = 1 if prediction == label else 0

        output[k] = {"img": fname, "softmax_dist": probs, "probs": probs_mean, "prediction": prediction, "truth": label, "uncertainty": uncertainty, "correct": correct}
        k+=1
    
    return output

2.4 Models

We will examine data gathered from four variants of LeNet-5. All models were trained on CIFAR-10 using the fastai API. CIFAR-10 contains 60.000 labelled 32x32x3 color images belonging to 10 different classes. The input data was split into a training set of 50.000 images and a test set of 10.000 images. The training set was further split into a training set and a validation set. All models have weight_decay = 0.0005 and all learning rates were chosen using lr_find:

  • model55: Trained for 60 epochs with a learning rate of 0.001 and kernel size = (5,5) and drop_rate = .5. 0.71280 validation loss at end of training with an accuracy of 0.76137 on the validation data. model55 represents the baseline implementation of LeNet-5. It is identical in structure to the one used by Gal et. al. [1].

  • model52: Trained for 14 epochs with a learning rate of 0.01 for the first 7 and 0.0001 on the remaining. Changed due to rapid overfitting. Model has kernel size = (5,5) and drop_rate = .2. 0.74186 validation loss at end of training with an accuracy of 0.74406 on the validation data.

  • model35: Trained for 66 epochs with a learning rate of 0.001 and kernel size = (3,3) and drop_rate = .5. 0.75483 validation loss at end of training with an accuracy of 0.74218 on the validation data.

  • model32: Trained for 52 epochs with a learning rate of 0.001 and kernel size = (3,3) and drop_rate = .2. 0.75449 validation loss at end of training with an accuracy of 0.74090 on the validation data.

Note that model55 has a slightly better baseline performance than the other models.

2.5 Data

The data contains the following variables after it has been prepared for analysis in R:

  • correct (logical): indicator the is TRUE if the predicted class label matches the true class label, else FALSE.

  • prediction (int): predicted class label.

  • truth (int): true class label.

  • uncertainty (dbl): empirical standard deviation of softmax values for predicted class.

  • prob1 (dbl): argmax of mean softmax output, i.e. mean probability of predicted class.

  • prob2 (dbl): mean probability of runner-up prediction.

  • class2 (int): class label of runner-up prediction.

  • logit_prob1 (dbl): logit transformation of prob1.

  • diff (dbl): prob1-prob2

  • diff_sd_ratio (dbl): diff/uncertainty.

All the variables above are pretty standard, with the exception of diff_sd_ratio. Intuitively, if diff is large, the averaged models all agree that class \(k\) is the correct prediction. If diff is small, the models sampled by MC dropout don’t agree on a single class. Thus diff also serves as a proxy for uncertainty. Model uncertainty, however, is approximated by the empirical standard deviation of the predictions for class \(k\). Thus diff_sd_ratio is expressed by \[\tau_{kj} = \frac{\hat{\mu}_k - \hat{\mu}_j}{\hat{\sigma}_k}\] where \(j\) is the runner-up prediction. \(\tau_{jk}\) gives us a ratio of two different measures of uncertainty.

3 Exploratory analysis

This section will be divided into to parts. First, we will examine the resulting data from model55, which will be regarded as our baseline model. Next, we will analyse the data from models obtained by varying kernel sizes and dropout rates.

3.1 Uncertainty analysis of baseline model

3.1.1 Entire data set

# Importing data
data <- as.tibble(read.csv("~/Documents/Masteroppgave/Data/Resultater/lenet-model55.csv"))
df <- select(data, -X)
# Summarizing entire data set
summary(df)
    correct         prediction        truth      uncertainty            prob1            prob2               class2     
 Min.   :0.0000   Min.   :0.000   Min.   :0.0   Min.   :0.0000029   Min.   :0.1835   Min.   :0.0000004   Min.   :0.000  
 1st Qu.:1.0000   1st Qu.:2.000   1st Qu.:2.0   1st Qu.:0.0400673   1st Qu.:0.5814   1st Qu.:0.0188524   1st Qu.:2.000  
 Median :1.0000   Median :5.000   Median :4.5   Median :0.1274580   Median :0.8281   Median :0.1021314   Median :4.000  
 Mean   :0.7916   Mean   :4.561   Mean   :4.5   Mean   :0.1184386   Mean   :0.7657   Mean   :0.1343779   Mean   :4.082  
 3rd Qu.:1.0000   3rd Qu.:7.000   3rd Qu.:7.0   3rd Qu.:0.1831702   3rd Qu.:0.9700   3rd Qu.:0.2257584   3rd Qu.:6.000  
 Max.   :1.0000   Max.   :9.000   Max.   :9.0   Max.   :0.3526330   Max.   :1.0000   Max.   :0.4989194   Max.   :9.000  
      diff           diff_sd_ratio       logit_prob1     
 Min.   :0.0000124   Min.   :     0.0   Min.   :-1.4929  
 1st Qu.:0.3340403   1st Qu.:     1.7   1st Qu.: 0.3284  
 Median :0.7213734   Median :     5.4   Median : 1.5722  
 Mean   :0.6312929   Mean   :   208.5   Mean   : 2.1424  
 3rd Qu.:0.9503560   3rd Qu.:    23.5   3rd Qu.: 3.4750  
 Max.   :0.9999990   Max.   :340800.1   Max.   :14.3329  

For the entire set of classifications, we have the following notable quantities:

  • The mean uncertainty of the predicted class is 0.1184386 and the median is 0.127458. The minimum value is 2.9342710^{-6}, the maximum is 0.352633. The interquartile range (IQR) is 0.1431029.

  • The mean softmax output of the predicted class is 0.7656708 and the median is 0.8280935. The minimum value is 0.1834866, the maximum is 0.9999994. The IQR is 0.3885904.

  • The mean softmax output of the runner-up is 0.1343779 and the median is 0.1021314. The minimum value is 4.111852810^{-7}, the maximum is 0.4989194. The IQR is 0.206906.

  • The mean difference between the softmax ouputs of the prediction and runner-up is 0.6312929 and the median is 0.7213734. The minimum value is 1.242756810^{-5}, the maximum is 0.999999. The IQR is 0.6163157.

  • The mean difference to uncertainty ratio, or \(\tau_{jk}\), is 208.5008833 and the median is 5.3560735. The minimum value is 5.325851410^{-5}, the maximum is 3.408001310^{5}. The IQR is 21.8301779.

3.1.2 Summary statistics

# Aggregating summary statistics by correct/incorrect
agg_df <- df %>% 
  group_by(correct) %>% 
  summarise(n=n(),
          mean_uncertainty=mean(uncertainty), 
          sd_uncertainty=sd(uncertainty), 
          mean_diff=mean(diff),
          sd_diff=sd(diff),
          mean_ratio=mean(diff_sd_ratio),
          sd_ratio=sd(diff_sd_ratio))
agg_df
  • The model has incorrectly classified 2084 images. The mean uncertainty of the predicted class is 0.1802444.

  • The model has correctly classified 7916 images. The mean estimated uncertainty is 0.1021673.

3.1.3 Incorrect classifications

# Summarizing incorrect predictions
incorrect_df <- df %>% 
  filter(correct==0)
summary(incorrect_df)
    correct    prediction        truth        uncertainty           prob1            prob2              class2           diff          
 Min.   :0   Min.   :0.000   Min.   :0.000   Min.   :0.001013   Min.   :0.1835   Min.   :0.000185   Min.   :0.000   Min.   :0.0000124  
 1st Qu.:0   1st Qu.:2.000   1st Qu.:2.000   1st Qu.:0.153040   1st Qu.:0.4103   1st Qu.:0.173676   1st Qu.:2.000   1st Qu.:0.0959377  
 Median :0   Median :4.000   Median :4.000   Median :0.181794   Median :0.5264   Median :0.239908   Median :4.000   Median :0.2403054  
 Mean   :0   Mean   :4.342   Mean   :4.049   Mean   :0.180244   Mean   :0.5451   Mean   :0.242866   Mean   :4.038   Mean   :0.3022316  
 3rd Qu.:0   3rd Qu.:6.000   3rd Qu.:5.000   3rd Qu.:0.210907   3rd Qu.:0.6602   3rd Qu.:0.312481   3rd Qu.:6.000   3rd Qu.:0.4645013  
 Max.   :0   Max.   :9.000   Max.   :9.000   Max.   :0.333903   Max.   :0.9994   Max.   :0.498919   Max.   :9.000   Max.   :0.9992357  
 diff_sd_ratio       logit_prob1     
 Min.   :  0.0001   Min.   :-1.4929  
 1st Qu.:  0.5130   1st Qu.:-0.3629  
 Median :  1.2146   Median : 0.1055  
 Mean   :  2.8113   Mean   : 0.2501  
 3rd Qu.:  2.4979   3rd Qu.: 0.6642  
 Max.   :986.4482   Max.   : 7.4530  

For the entire set of incorrect classifications, we have the following notable quantities:

  • The mean uncertainty of the predicted class is 0.1802444 and the median is 0.1817945. The minimum value is 0.001013, the maximum is 0.333903. The interquartile range (IQR) is 0.057867.

  • The mean softmax output of the predicted class is 0.545098 and the median is 0.5263628. The minimum value is 0.1834866, the maximum is 0.9994206. The IQR is 0.2499404.

  • The mean softmax output of the runner-up is 0.2428664 and the median is 0.2399082. The minimum value is 1.84973710^{-4}, the maximum is 0.4989194. The IQR is 0.1388051.

  • The mean difference between the softmax ouputs of the prediction and runner-up is 0.3022316 and the median is 0.2403054. The minimum value is 1.242756810^{-5}, the maximum is 0.9992357. The IQR is 0.3685635.

  • The mean difference to uncertainty ratio, or \(\tau_{jk}\), is 2.8112788 and the median is 1.2145691. The minimum value is 5.325851410^{-5}, the maximum is 986.4482162. The IQR is 1.9848617.

3.1.4 Correct classifications

# Summarizing correct predictions
correct_df <- df %>% 
  filter(correct==1)
summary(correct_df)
    correct    prediction        truth        uncertainty            prob1            prob2               class2           diff          
 Min.   :1   Min.   :0.000   Min.   :0.000   Min.   :0.0000029   Min.   :0.1863   Min.   :0.0000004   Min.   :0.000   Min.   :0.0007845  
 1st Qu.:1   1st Qu.:2.000   1st Qu.:2.000   1st Qu.:0.0263485   1st Qu.:0.7009   1st Qu.:0.0114910   1st Qu.:2.000   1st Qu.:0.5144494  
 Median :1   Median :5.000   Median :5.000   Median :0.0965002   Median :0.9002   Median :0.0618456   Median :4.000   Median :0.8352473  
 Mean   :1   Mean   :4.619   Mean   :4.619   Mean   :0.1021673   Mean   :0.8237   Mean   :0.1058168   Mean   :4.094   Mean   :0.7179230  
 3rd Qu.:1   3rd Qu.:7.000   3rd Qu.:7.000   3rd Qu.:0.1672548   3rd Qu.:0.9820   3rd Qu.:0.1758222   3rd Qu.:7.000   3rd Qu.:0.9697941  
 Max.   :1   Max.   :9.000   Max.   :9.000   Max.   :0.3526330   Max.   :1.0000   Max.   :0.4886857   Max.   :9.000   Max.   :0.9999990  
 diff_sd_ratio       logit_prob1     
 Min.   :     0.0   Min.   :-1.4742  
 1st Qu.:     2.8   1st Qu.: 0.8516  
 Median :     8.6   Median : 2.1997  
 Mean   :   262.7   Mean   : 2.6406  
 3rd Qu.:    36.9   3rd Qu.: 3.9965  
 Max.   :340800.1   Max.   :14.3329  

For the entire set of correct classifications, we have the following notable quantities:

  • The mean uncertainty of the predicted class is 0.1021673 and the median is 0.0965002. The minimum value is 2.9342710^{-6}, the maximum is 0.352633. The interquartile range (IQR) is 0.1409062.

  • The mean softmax output of the predicted class is 0.8237397 and the median is 0.9002195. The minimum value is 0.1863078, the maximum is 0.9999994. The IQR is 0.2810591.

  • The mean softmax output of the runner-up is 0.1058168 and the median is 0.0618456. The minimum value is 4.111852810^{-7}, the maximum is 0.4886857. The IQR is 0.1643312.

  • The mean difference between the softmax ouputs of the prediction and runner-up is 0.717923 and the median is 0.8352473. The minimum value is 7.844567310^{-4}, the maximum is 0.999999. The IQR is 0.4553448.

  • The mean difference to uncertainty ratio, or \(\tau_{jk}\), is 262.6516078 and the median is 8.5538778. The minimum value is 0.0029356, the maximum is 3.408001310^{5}. The IQR is 34.0877437.

3.2 Distribution of uncertainty

In the following we will visualize the relationships between our variabels. We start by examining the empirical distribution of the uncertainty estimates \(\hat{\sigma}_k\).

3.2.1 Full distribution

The distribution appears to be bimodal, with peaks close to 0 and 0.2:

# Distribution of estimated uncertainty
p1 <- df %>% 
  ggplot(aes(x=uncertainty)) +
  geom_histogram(col="grey", bins = 50, alpha=.5) +
  ggtitle("Distribution of estimated uncertainty")
p1

3.2.2 Uncertainty by prediction

By grouping the uncertainty estimates by correct (i.e. if the label was correctly predicted or not), we can find out how the predictions contribute to the uncertainty distribution:

#Distribution of estimated uncertainty by prediction
p2 <- df %>% 
  ggplot(aes(x=uncertainty, col=as.factor(correct))) +
  geom_freqpoly(alpha=.7) +
  ggtitle("Distribution of estimated uncertainty by classification") +
  scale_color_discrete(name="Prediction",
                         breaks=c("0", "1"),
                         labels=c("0: Incorrect", "1: Correct"))
p2

The blue line corresponds to the correct predictions, the red line corresponds to incorrect predictions. We see that the incorrect predictions are centered around a higher associated uncertainty, whereas far more of the correctly predicted classes are concentrated around a low uncertainty value. The incorrect classifications greatly contribute to the bimodality, but it is also present in the distribution of uncertainty for the correct classifications.

3.2.3 Kernel density estimates

The following kernel density estimate plot (using a Gaussian kernel) gives us an idea of how the distributions compare to eachother:

# KDE by correct prediction
p3 <- df %>% 
  ggplot(aes(x=uncertainty)) +
  geom_density(data=subset(df, correct==0), fill="red", alpha=I(.2)) +
  geom_density(data=subset(df, correct==1), fill="turquoise", alpha=I(.2)) +
  ggtitle("Kernel density estimates of uncertainty distribution")
p3

3.2.4 Boxplots

The boxplot gives us yet another way to visualize the difference between uncertainty distributions by predictive ability:

# Boxplot of uncertainties for correct vs. incorrect
p4 <- df %>% 
  ggplot(aes(x=as.factor(correct), y=uncertainty)) +
  geom_boxplot(aes(fill=as.factor(correct)), alpha=.7) +
  labs(x="correct") +
  ggtitle("Boxplot of uncertainty distribution by correct/incorrect") +
  scale_fill_discrete(name="Prediction",
                         breaks=c("0", "1"),
                         labels=c("0: Incorrect", "1: Correct"))
p4

3.3 Relationship to other variables

3.3.1 Softmax of predicted class

We may also be interested in the relationship between uncertainty and other variables. First, we plot uncertainty against prob1 (the prediction’s softmax output):

# Plotting softmax output of prediction against estimated uncertainty
p5 <- df %>% 
  ggplot(aes(x=prob1, y=uncertainty)) +
  geom_point(alpha=.2) +
  ggtitle("Uncertainty vs. softmax output of prediction for all observations") +
  labs(x="softmax output of predicted class") +
  scale_color_distiller(name="Softmax output",
                        palette = "Spectral")
p5

The softmax outputs in the above plot are colour graded. Outputs close to 1 are red, outputs close to 0 are blue. The plot shows a clear parabolic shape.

3.3.2 By classification

p6 <- df %>% 
  mutate(correct=as.logical(correct)) %>% 
  ggplot(aes(x=prob1, y=uncertainty)) +
  geom_point(aes(fill=correct, col=correct), shape=21, alpha=.5) +
  ggtitle("Uncertainty vs. softmax output of prediction for all observations") +
  labs(x="softmax output of predicted class")
p6

3.3.3 Predictions and runners-up

We can obtain more information by colouring the points by the value of the runner-up predictions:

# Plotting softmax output of prediction against estimated uncertainty, coloured by softmax output of runner-up
p7 <- df %>% 
  ggplot(aes(x=prob1, y=uncertainty, col=prob2)) +
  geom_point(alpha=.5) +
  ggtitle("Uncertainty vs. softmax output of prediction for all observations") +
  labs(x="softmax output of predicted class") +
  scale_color_distiller(name="Runner up",
                        palette = "Spectral")
p7

This plot is particularly interesting. The softmax outputs of the runner-up classes \(\hat{\mu}_j\) in the above plot are colour graded. \(\hat{\mu}_j \approx 0.5\) are red, \(\hat{\mu}_j \approx 0\) are blue. We see a clear concentration of red points in the area where the probability of the predictied class \(\hat{\mu}_k \approx 0.5\). If the softmax predictions of both the predicted class and the runner-up are close 0.5, then we have a situation analogous to maximum entropy. This points coincide with the largest approximated uncertainty values.

3.3.4 Softmax of prediction by classification

In the following plot we split the observations by incorrect/correct predictions, and plot the values of uncertainty against the softmax output of the predicted class:

# Plotting uncertainty vs. softmax output of prediction
p8 <- df %>% 
  ggplot(aes(x=prob1, y=uncertainty)) +
  geom_point(aes(col=prob2),alpha=0.5) +
  geom_density_2d(col="black", alpha=.3) +
  ggtitle("Uncertainty vs. softmax output of prediction by incorrect/correct") +
  labs(x="softmax output of prediction") +
  facet_grid(.~as.factor(correct)) +
  scale_color_distiller(name="Runner up",
                        palette = "Spectral")
p8

The black contour lines indicate where most of the points are concentrated. The plot on the left is for incorrect predictions. The right hand plot represents the correct predictions. For the correct predictions, it seems as if far more of the points are concentrated around high predicted output/low runner-up output/low uncertainty. This is not surprising, considering 75% of the correct predictions have a softmax value of approximately 0.7 or above. For the incorrect classifications, most of the points are concentrated around the area of maximum entropy. This indicates that the approximated uncertainty estimates indeed contain valuable information in the incorrect cases.

3.3.5 Runner-up predictions

The following plot shows the relationship between uncertainty estimates and the softmax output of the runner-up prediction. Unsurprisingly, model uncertainty increases as the softmax output of the runner-up increases. We have plotted a LOESS estimate of the mean uncertainty as a function of the runner-up output to make this clearer:

# Plotting softmax output of runner-up against estimated uncertainty
p10 <- df %>% 
  ggplot(aes(x=prob2, y=uncertainty)) +
  geom_point(alpha=.2) +
  geom_smooth(method="loess") +
  labs(x="softmax output of runner-up") +
  ggtitle("Uncertainty vs. softmax output of runner-up")
p10

3.4 Images associated with high uncertainty

The following plots were generated using Python (code available on GitHub). On the left hand side we see the unnormalized image with the corresponding ground truth label. The plot in the middle shows the softmax output of the predicted class for each of the \(T=100\) stochastic forward passes. \(\mu_k\) is given by the solid red line, \(\mu_j\) is given by the dashed brown line. The plot title shows both the predicted class and the runner-up class. To the right is a kernel density estimate (using a Gaussian kernel) of the \(T=100\) softmax outputs for the predicted class. The plots show the top 5 most uncertain classifications in the entire data set.

3.4.1 Incorrect

4

5

6

7

8

8.0.1 Correct

9

10

11

12

13

For some unknown reason, the runner-up prediction for the five most uncertain but correct predictions are all “airplane”. One possible explanation is the presence of large areas of blue and/or white, which we might assume would be present as backgrounds in images of planes flying through the sky.

13.1 Uncertainty-prediction correlation

As mentioned in section 2.2, the connection established between dropout neural networks and GPs are lost when applied to convolutional neural networks. Performing a logistic regression gives us a simple way of testing if the approximated uncertainty is a significant predictor of the model’s ability to predict correctly:

# Fitting logistic regression model to check significance of uncertainty
model_sd <- glm(as.factor(correct)~uncertainty, data=df, family = binomial(link="logit"))
summary(model_sd)

Call:
glm(formula = as.factor(correct) ~ uncertainty, family = binomial(link = "logit"), 
    data = df)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.6704   0.2409   0.3588   0.7100   2.0114  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)    
(Intercept)   3.55249    0.07625   46.59   <2e-16 ***
uncertainty -15.40803    0.43250  -35.63   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 10236.6  on 9999  degrees of freedom
Residual deviance:  8467.6  on 9998  degrees of freedom
AIC: 8471.6

Number of Fisher Scoring iterations: 5

The coefficient associated with uncertainty is highly significant, indicating that the estimates gathered from performing MC dropout are indeed a useful quantification of predictive uncertainty. Given a unit increase in uncertainty, we expect the probability of predicting correctly to decrease.

13.2 Referral criteria

The question is then: How do we determine a reasonable uncertainty value for referral to a human expert? As we saw in section 3.1.2, the mean uncertainty values of the incorrect predictions higher than for the correct predictions (0.1802444 vs. 0.1021673, respectively).

Naively, we could set the threshold for referral to the mean uncertainty of the incorrectly classified images:

# Counting number of correct/incorrect by uncertainty >= .18 
referral <- df %>% 
  filter(uncertainty>=.18) %>% 
  count(correct)
referral

The problem here is apparent: Many of the correctly classified images are associated with a relatively high level of uncertainty (in our case, this is due to the bimodality of the uncertainty distribution in section 3.2.1). We speculate that although uncertainty seems to contain valueable information, the relative uncertainties of the correct/incorrect observations (in this case) may be too small to differentiate which images should be referred to an expert. We may need to amplify the quantification of uncertainty in some way.

One possible approach is to incorporate information from the value of \(\tau_{jk}\), which we introduced in section 2.5. Consider the following cases:

  • \(\tau_{kj} \approx 1\) means that diff and uncertainty are relatively similar. This happens if a) the models have failed to reach a consensus (diff is small) but model uncertainty is low, or b) the models have reached a consensus (diff is large) but model uncertainty is high. Let’s call these referral predictions.

  • \(\tau_{kj} \to 0\) means that uncertainty is much larger than diff. These should represent uncertain predictions.

  • \(\tau_{kj} \to \infty\) means that uncertainty is much smaller than diff. These should represent non-referral predictions.

As we saw in table 3.1.2, the mean \(\tau_{jk}\) for the incorrectly classified images is 2.8112788 and 262.6516078 for the correctly classified images. Setting the referral threshold to the mean \(\tau_{jk}\) for the incorrect classifications (2.8112788), we get:

# Counting number of correct/incorrect by tau <= 2.8
referral <- df %>% 
  filter(diff_sd_ratio <= 2.8) %>% 
  count(correct)
referral

We run into the same problem as when we used the mean uncertainty: Many of the correctly predicted images would be referred, in fact more than when we used the uncertainty estimates. It is interesting to note, however, that we get far more referrals of incorrectly classified images. Again, this may indicate that \(\tau_{jk}\) is a useful measure of uncertainty.

Setting the referral rate of the mean \(\tau_{jk}\) to 1 gives us the following:

# Counting number of correct/incorrect by tau <= 1
referral <- df %>% 
  filter(diff_sd_ratio <= 1) %>% 
  count(correct)
referral

This is a clear improvement over our first approach, in terms of the amount of referred images. 161 fewer incorrect images are referred compared to 889 fewer correct images. This could indicate that \(\tau_{jk}\) is a useful quantification of uncertainty.

BRAINSTORMING, USING RUNNERS-UP:

# What is accuracy if we use diff_sd_ratio and runner-up as prediction?
referral <- df %>% 
  mutate(correct=replace(correct, diff_sd_ratio<=1 & correct==0 & class2==truth, 1)) %>% 
  count(correct)
# What is accuracy if we use uncertainty and runner-up as prediction?
referral <- df %>% 
  mutate(correct=replace(correct, uncertainty>=.18 & correct==0 & class2==truth, 1)) %>% 
  count(correct)

IDEA: Using LDA to find best tau.

df %>% 
  ggplot(aes(x=log(diff_sd_ratio))) +
  geom_histogram(aes(fill=as.factor(correct)))

library(MASS)

Attaching package: ‘MASS’

The following object is masked from ‘package:dplyr’:

    select
lda_fit <- lda(as.factor(correct)~log(diff_sd_ratio), data=df)
lda_fit
Call:
lda(as.factor(correct) ~ log(diff_sd_ratio), data = df)

Prior probabilities of groups:
     0      1 
0.2084 0.7916 

Group means:
  log(diff_sd_ratio)
0         0.05615931
1         2.44256142

Coefficients of linear discriminants:
                         LD1
log(diff_sd_ratio) 0.5108872

13.3 Usefulness of runner-up predictions

For the most uncertain incorrectly classified images the runner-up suggestions are non-sensical. Still, is there any information to be obtained from the runner-up predictions? What would happen to our overall accuracy if we used the runner-up predictions for all incorrect classifications?

# Counting classification accuracy if runner-up is equal to ground truth
class2_df <- df %>%
  mutate(correct=replace(correct, correct==0 & class2==truth, 1)) %>% 
  count(correct)
class2_df

Accuracy would rise to 0.9079. This indicates that there may be some valuable information to be gathered from the runner-up predictions. So how do we determine which images to take a closer look at?

13.4 Using the difference-uncertainty ratio

# Fitting logistic regression model check significance of log-transformed diff_sd_ratio
model_ratio <- glm(as.factor(correct)~log(diff_sd_ratio), data=df, family = binomial(link="logit"))
summary(model_ratio)

Call:
glm(formula = as.factor(correct) ~ log(diff_sd_ratio), family = binomial(link = "logit"), 
    data = df)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-3.5499   0.0773   0.3446   0.6300   3.0568  

Coefficients:
                   Estimate Std. Error z value Pr(>|z|)    
(Intercept)         0.36023    0.03255   11.07   <2e-16 ***
log(diff_sd_ratio)  0.86145    0.02230   38.63   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 10236.6  on 9999  degrees of freedom
Residual deviance:  7612.8  on 9998  degrees of freedom
AIC: 7616.8

Number of Fisher Scoring iterations: 6
# Checking for interactions between uncertainty and log-transformed diff_sd_ratio
model_inter <- glm(as.factor(correct)~uncertainty*log(diff_sd_ratio), data=df, family = binomial(link="logit"))
summary(model_inter)

Call:
glm(formula = as.factor(correct) ~ uncertainty * log(diff_sd_ratio), 
    family = binomial(link = "logit"), data = df)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-4.5057   0.0137   0.2455   0.7045   2.8495  

Coefficients:
                               Estimate Std. Error z value Pr(>|z|)    
(Intercept)                    -0.24145    0.15900  -1.519  0.12887    
uncertainty                     2.81600    0.80255   3.509  0.00045 ***
log(diff_sd_ratio)              1.51152    0.07259  20.823  < 2e-16 ***
uncertainty:log(diff_sd_ratio) -4.49845    0.38959 -11.547  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 10236.6  on 9999  degrees of freedom
Residual deviance:  7454.4  on 9996  degrees of freedom
AIC: 7462.4

Number of Fisher Scoring iterations: 7
# Checking models
AIC(model_sd, model_ratio, model_inter)

14 Comparison of all models

We gather the data from all models in a single dataframe df:

ROOT <- "~/Documents/Masteroppgave/Data/Resultater/"
# Kernel size 3, drop rate = .2
df32 <- process(ROOT, "lenet-model32.csv", "model32", 3, .2)
# Kernel size 3, drop rate = .5
df35 <- process(ROOT, "lenet-model35.csv", "model35", 3, .5)
# Kernel size 5, drop rate = .5
df55 <- process(ROOT, "lenet-model55.csv", "model55", 5, .5)
# Kernel size 5, drop rate = .2
df52 <- process(ROOT, "lenet-model52.csv", "model52", 5, .2)
# Combining all dataframes and adding kernel size, dropout rate
df_all <- df32 %>% 
  bind_rows(df35) %>% 
  bind_rows(df52) %>% 
  bind_rows(df55) %>% 
  mutate(correct=as.factor(as.logical(correct)),
         kernel=as.factor(as.character(kernel)),
         dropout=as.factor(as.character(dropout)))
head(df_all)
summary(df_all)
       X         correct        prediction        truth      uncertainty            prob1            prob2               class2     
 Min.   :   0   FALSE: 9160   Min.   :0.000   Min.   :0.0   Min.   :0.0000013   Min.   :0.1694   Min.   :0.0000004   Min.   :0.000  
 1st Qu.:2500   TRUE :30840   1st Qu.:2.000   1st Qu.:2.0   1st Qu.:0.0367498   1st Qu.:0.5685   1st Qu.:0.0234855   1st Qu.:2.000  
 Median :5000                 Median :5.000   Median :4.5   Median :0.1041035   Median :0.8069   Median :0.1119699   Median :4.000  
 Mean   :5000                 Mean   :4.568   Mean   :4.5   Mean   :0.1025274   Mean   :0.7530   Mean   :0.1398601   Mean   :4.098  
 3rd Qu.:7499                 3rd Qu.:7.000   3rd Qu.:7.0   3rd Qu.:0.1551265   3rd Qu.:0.9624   3rd Qu.:0.2325295   3rd Qu.:6.000  
 Max.   :9999                 Max.   :9.000   Max.   :9.0   Max.   :0.3526330   Max.   :1.0000   Max.   :0.4989194   Max.   :9.000  
      diff           diff_sd_ratio       logit_prob1         model           kernel    dropout    
 Min.   :0.0000124   Min.   :     0.0   Min.   :-1.5899   Length:40000       3:20000   0.2:20000  
 1st Qu.:0.3084196   1st Qu.:     1.9   1st Qu.: 0.2757   Class :character   5:20000   0.5:20000  
 Median :0.6873797   Median :     5.8   Median : 1.4300   Mode  :character                        
 Mean   :0.6131886   Mean   :   242.5   Mean   : 1.9877                                           
 3rd Qu.:0.9374409   3rd Qu.:    25.3   3rd Qu.: 3.2416                                           
 Max.   :0.9999992   Max.   :798986.9   Max.   :15.0261                                           

Note that we have added two new variables to the data set: kernel (factor, convolution size) and dropout (factor, dropout rate). In the following chunk we generate some summary statistics:

# Summary stats for all models
total_uncertainty <- df_all %>% 
  group_by(model) %>% 
  summarise(accuracy=sum(as.numeric(correct)-1)/10000,
          mean_uncertainty=mean(uncertainty),
          sd_uncertainty=sd(uncertainty), 
          mean_diff=mean(diff),
          sd_diff=sd(diff),
          mean_ratio=mean(diff_sd_ratio),
          sd_ratio=sd(diff_sd_ratio)) %>% 
  dplyr::arrange(mean_uncertainty)
total_uncertainty

Overall:

# Aggregating summary statistics by model and correct/incorrect
agg_df_all <- df_all %>% 
  group_by(model, correct) %>% 
  summarise(p=n()/10000,
          mean_uncertainty=mean(uncertainty),
          sd_uncertainty=sd(uncertainty), 
          mean_diff=mean(diff),
          sd_diff=sd(diff),
          mean_ratio=mean(diff_sd_ratio),
          sd_ratio=sd(diff_sd_ratio))
# Sorting by uncertainty: most certain to least certain
ordered_df <- agg_df_all %>% 
  dplyr::arrange(mean_uncertainty)
ordered_df

14.1 Visualisations

14.1.1 Distributions of uncertainty

# Distributions of uncertainty by model (left) and by model/classification (right)
p11 <- df_all %>% 
  ggplot(aes(x=uncertainty)) + 
  geom_histogram(alpha=.5, fill="blue", bins=50) +
  facet_grid(as.factor(model)~.) +
  ggtitle("Distributions of uncertainty")
p12 <- df_all %>% 
  ggplot(aes(x=uncertainty, col=correct)) +
  geom_freqpoly(alpha=.7) +
  ggtitle("Distribution of estimated uncertainty by classification") +
  scale_color_discrete(name="Prediction",
                         breaks=c("0", "1"),
                         labels=c("0: Incorrect", "1: Correct")) +
  facet_grid(as.factor(model)~.)
layout1 <- matrix(c(1,2), nrow=1)
multiplot(p11, p12, layout=layout1)

14.1.2 KDEs of uncertainty

# KDEs
p13 <- df_all %>% 
  ggplot(aes(x=uncertainty)) + 
  geom_density(aes(fill=correct), alpha=.7) +
  facet_grid(as.factor(model)~correct) +
  ggtitle("Kernel density estimations of uncertainty by classification")
p13

14.1.3 Boxplots

# Boxplots of uncertainties
p14 <- df_all %>% 
  ggplot(aes(x=correct, y=uncertainty)) +
  geom_boxplot(aes(fill=correct), alpha=.7) +
  facet_grid(.~as.factor(model))
# Boxplots of log-transformed tau by model and classification
p15 <- df_all %>% 
  ggplot(aes(x=correct, y=log(diff_sd_ratio))) +
  geom_boxplot(aes(fill=correct), alpha=.7) +
  facet_grid(.~as.factor(model))
layout2 <- matrix(c(2,1), byrow=TRUE)
multiplot(p14, p15, layout=layout2)

14.1.4 Uncertainty vs. softmax ouput

# Plotting uncertainty against softmax of predicted class, with density estimation contours
p16 <- df_all %>% 
  ggplot(aes(x=prob1, y=uncertainty, col=prob2)) +
  geom_point(alpha=.5) +
  labs(y="softmax output of predicted class") +
  scale_color_distiller(name="",
                        palette = "Spectral") +
  facet_grid(~as.factor(model)~correct)
p16

14.1.5 Uncertainty vs. softmax ouput with contour curves

p17 <- df_all %>% 
  ggplot(aes(x=prob1, y=uncertainty, col=prob2)) +
  geom_point(alpha=.5) +
  geom_density2d(col="black", alpha=.5) +
  labs(y="softmax output of predicted class") +
  scale_color_distiller(name="",
                        palette = "Spectral") +
  facet_grid(~as.factor(model)~correct)
p17

15 References

[1] Gal, Y. & Ghahramani, Z. Bayesian Convolutional Neural Networks with Bernoulli Approximate Variational Inference. arXiv: 1506.02158 (2015).

[2] Gal, Y. & Ghahramani, Z. Dropout as a Bayesian Approximation: Representing Model Uncertainty in Deep Learning. arXiv: 1506.02142 (2015).

[3] Gal, Y. & Ghahramani, Z. Dropout as a Bayesian Approximation: Appendix. arXiv: 1506.02157 (2015).

[4] Leibig, C. & Allken, V. et. al. Leveraging uncertainty information from deep neural networks for disease detection. Scientific Reports volume 7, Article number: 17816 (2017).

[5] Hinton, G. & Srivastava, N. et. al. Improving neural networks by preventing co-adaptation of feature detectors. arXiv: 1207.0580 (2012).

[6] Leibig, C. & Allken, V. et. al. Leveraging uncertainty information from deep neural networks for disease detection: supplementary material. Scientific Reports volume 7, Article number: 17816 (2017).

LS0tCnRpdGxlOiAiQW5hbHlzaXMgb2YgYXBwcm94aW1hdGVkIHVuY2VydGFpbnR5IGluIGRlZXAgbGVhcm5pbmciCmF1dGhvcjogIlNlYW4gTWVsaW5nIE11cnJheSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogeWVzCiAgaHRtbF9ub3RlYm9vazoKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0b2M6IHllcwotLS0KCioqKgojIFNldHVwCkluIHRoaXMgbm90ZWJvb2sgd2UgdXNlIHRoZSBmb2xsb3dpbmcgUiBwYWNrYWdlczogYHRpZHl2ZXJzZWAgYW5kIGBnZ3Bsb3QyYC4gSW4gYWRkaXRpb24gd2UgdXNlIHRoZSBgbXVsdGlwbG90YCBmdW5jdGlvbmFsaXR5IHByb3ZpZGVkIGJ5IFtDb29rYm9vayBmb3IgUl0oaHR0cDovL3d3dy5jb29rYm9vay1yLmNvbS9HcmFwaHMvTXVsdGlwbGVfZ3JhcGhzX29uX29uZV9wYWdlXyhnZ3Bsb3QyKS8pLiBJbiBQeXRob24gd2UgdXNlIGBmYXN0YWlgLCBgbnVtcHlgIGFuZCBgcGFuZGFzYC4gRnVsbCBjb2RlIGZvciB0cmFpbmluZyBhbmQgaW5mZXJlbmNlIFthdmFpbGFibGUgYXQgR2l0SHViXShsaW5rKS4gQWxsIGNvZGUgZm9yIHN0YXRpc3RpY2FsIGFuYWx5c2lzIGlzIGluY2x1ZGVkIGluIHRoaXMgZG9jdW1lbnQuCgpgYGB7ciBlY2hvPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KIyBMb2FkaW5nIHVzZWZ1bCBwYWNrYWdlcwpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ3Bsb3QyKQoKIyBNdWx0aXBsZSBwbG90IGZ1bmN0aW9uCiMKIyBnZ3Bsb3Qgb2JqZWN0cyBjYW4gYmUgcGFzc2VkIGluIC4uLiwgb3IgdG8gcGxvdGxpc3QgKGFzIGEgbGlzdCBvZiBnZ3Bsb3Qgb2JqZWN0cykKIyAtIGNvbHM6ICAgTnVtYmVyIG9mIGNvbHVtbnMgaW4gbGF5b3V0CiMgLSBsYXlvdXQ6IEEgbWF0cml4IHNwZWNpZnlpbmcgdGhlIGxheW91dC4gSWYgcHJlc2VudCwgJ2NvbHMnIGlzIGlnbm9yZWQuCiMKIyBJZiB0aGUgbGF5b3V0IGlzIHNvbWV0aGluZyBsaWtlIG1hdHJpeChjKDEsMiwzLDMpLCBucm93PTIsIGJ5cm93PVRSVUUpLAojIHRoZW4gcGxvdCAxIHdpbGwgZ28gaW4gdGhlIHVwcGVyIGxlZnQsIDIgd2lsbCBnbyBpbiB0aGUgdXBwZXIgcmlnaHQsIGFuZAojIDMgd2lsbCBnbyBhbGwgdGhlIHdheSBhY3Jvc3MgdGhlIGJvdHRvbS4KIwptdWx0aXBsb3QgPC0gZnVuY3Rpb24oLi4uLCBwbG90bGlzdD1OVUxMLCBmaWxlLCBjb2xzPTEsIGxheW91dD1OVUxMKSB7CiAgbGlicmFyeShncmlkKQoKICAjIE1ha2UgYSBsaXN0IGZyb20gdGhlIC4uLiBhcmd1bWVudHMgYW5kIHBsb3RsaXN0CiAgcGxvdHMgPC0gYyhsaXN0KC4uLiksIHBsb3RsaXN0KQoKICBudW1QbG90cyA9IGxlbmd0aChwbG90cykKCiAgIyBJZiBsYXlvdXQgaXMgTlVMTCwgdGhlbiB1c2UgJ2NvbHMnIHRvIGRldGVybWluZSBsYXlvdXQKICBpZiAoaXMubnVsbChsYXlvdXQpKSB7CiAgICAjIE1ha2UgdGhlIHBhbmVsCiAgICAjIG5jb2w6IE51bWJlciBvZiBjb2x1bW5zIG9mIHBsb3RzCiAgICAjIG5yb3c6IE51bWJlciBvZiByb3dzIG5lZWRlZCwgY2FsY3VsYXRlZCBmcm9tICMgb2YgY29scwogICAgbGF5b3V0IDwtIG1hdHJpeChzZXEoMSwgY29scyAqIGNlaWxpbmcobnVtUGxvdHMvY29scykpLAogICAgICAgICAgICAgICAgICAgIG5jb2wgPSBjb2xzLCBucm93ID0gY2VpbGluZyhudW1QbG90cy9jb2xzKSkKICB9CgogaWYgKG51bVBsb3RzPT0xKSB7CiAgICBwcmludChwbG90c1tbMV1dKQoKICB9IGVsc2UgewogICAgIyBTZXQgdXAgdGhlIHBhZ2UKICAgIGdyaWQubmV3cGFnZSgpCiAgICBwdXNoVmlld3BvcnQodmlld3BvcnQobGF5b3V0ID0gZ3JpZC5sYXlvdXQobnJvdyhsYXlvdXQpLCBuY29sKGxheW91dCkpKSkKCiAgICAjIE1ha2UgZWFjaCBwbG90LCBpbiB0aGUgY29ycmVjdCBsb2NhdGlvbgogICAgZm9yIChpIGluIDE6bnVtUGxvdHMpIHsKICAgICAgIyBHZXQgdGhlIGksaiBtYXRyaXggcG9zaXRpb25zIG9mIHRoZSByZWdpb25zIHRoYXQgY29udGFpbiB0aGlzIHN1YnBsb3QKICAgICAgbWF0Y2hpZHggPC0gYXMuZGF0YS5mcmFtZSh3aGljaChsYXlvdXQgPT0gaSwgYXJyLmluZCA9IFRSVUUpKQoKICAgICAgcHJpbnQocGxvdHNbW2ldXSwgdnAgPSB2aWV3cG9ydChsYXlvdXQucG9zLnJvdyA9IG1hdGNoaWR4JHJvdywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXlvdXQucG9zLmNvbCA9IG1hdGNoaWR4JGNvbCkpCiAgICB9CiAgfQp9CgojIERhdGEgY2xlYW5pbmcgZnVuY3Rpb24KcHJvY2VzcyA8LSBmdW5jdGlvbihST09ULCBmaWxlbmFtZSwgbmFtZSwga2VybmVsX3N6LCBkcm9wX3JhdGUpewogIFJPT1QgPC0gIn4vRG9jdW1lbnRzL01hc3Rlcm9wcGdhdmUvRGF0YS9SZXN1bHRhdGVyLyIKICBQQVRIIDwtIHBhc3RlKFJPT1QsIGZpbGVuYW1lLCBzZXA9IiIpCiAgZGF0IDwtIGFzLnRpYmJsZShyZWFkLmNzdihhcy5jaGFyYWN0ZXIoUEFUSCkpKQogICNkYXQgPC0gc2VsZWN0KGRhdCwgLVgpCiAgZGF0IDwtIGRhdCAlPiUgCiAgICBtdXRhdGUobW9kZWw9bmFtZSwKICAgICAgICAgICBrZXJuZWw9a2VybmVsX3N6LAogICAgICAgICAgIGRyb3BvdXQ9ZHJvcF9yYXRlKQogIHJldHVybihkYXQpCn0KYGBgCiMgSW50cm9kdWN0aW9uCgpUaGUgc3VjY2VzcyBvZiBkZWVwIGxlYXJuaW5nIG1vZGVscyBoYXMgdHlwaWNhbGx5IGJlZW4gbWVhc3VyZWQgaW4gdGVybXMgb2YgdGhlaXIgcHJlZGljdGl2ZSBwb3dlciwgYnV0IHRoZXkgaGF2ZSBsYWNrZWQgYSBwcmluY2lwbGVkIHdheSBvZiBleHByZXNzaW5nIHVuY2VydGFpbnR5IGFib3V0IHRoZXNlIHByZWRpY3Rpb25zLgoKSW4gbXkgbWFzdGVyJ3MgdGhlc2lzIHdlIGFwcGx5IHJlY2VudCBpbnNpZ2h0cyBjb25uZWN0aW5nIGRyb3BvdXQgbmV1cmFsIG5ldHdvcmtzIHRvIFtCYXllc2lhbiBhcHByb3hpbWF0ZSB2YXJpYXRpb25hbCBpbmZlcmVuY2UgKFZJKV0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzE2MDEuMDA2NzApLiBWSSBpcyB0ZWNobmlxdWUgZm9yIGFwcHJveGltYXRpbmcgaW50cmFjdGFibGUgaW50ZWdyYWxzIHRoYXQgYXJpc2Ugd2hlbiBtb2RlbGxpbmcgdGhlIHBvc3RlcmlvciBkaXN0cmlidXRpb24gaW4gQmF5ZXNpYW4gaW5mZXJlbmNlIGFuZCBtYWNoaW5lIGxlYXJuaW5nLiBHYWwgZXQuIGFsLiBbMSwgMiwgM10gaGF2ZSBzaG93biB0aGF0IHRoZSBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uIG9mIGEgTk4gY2FuIGJlIGFwcHJveGltYXRlZCBieSBsZWF2aW5nIGRyb3BvdXQgb24gYXQgdGVzdCB0aW1lIGFuZCBzYW1wbGluZyBtdWx0aXBsZSBwcmVkaWN0aW9ucy4gVGhpcyBhbW91bnRzIHRvIGRyYXdpbmcgTW9udGUgQ2FybG8gKE1DKSBzYW1wbGVzIGZyb20gdGhlIHBvc3RlcmlvciAoYSBicmllZiBkZXNjcmlwdGlvbiBvZiB0aGlzIHByb2Nlc3MgaXMgbGlzdGVkIGluIHNlY3Rpb24gMS4xKS4gVGhlIEJheWVzaWFuIGZyYW1ld29yayBhbGxvd3MgdXMgdG8gb2J0YWluIHByaW5jaXBsZWQgdW5jZXJ0YWludHkgZXN0aW1hdGVzIHdoZW4gbWFraW5nIHByZWRpY3Rpb25zIGluIHRoZXNlIHNvIGNhbGxlZCAqTUMgZHJvcG91dCogTk5zLiAKClRoZSByZXN1bHRzIG9mIFsxLDIsM10gc2VlbSBwYXJ0aWN1bGFybHkgcHJvbWlzaW5nIHdoZW4gYXBwbGllZCB0byBoZWFsdGhjYXJlLiBJbiBmYWN0LCBhIHJlY2VudCBwYXBlciBwdWJsaXNoZWQgaW4gTmF0dXJlIFs0XSBhcHBsaWVzIE1DIGRyb3BvdXQgdG8gY2FwdHVyZSB1bmNlcnRhaW50eSBlc3RpbWF0ZXMgd2hlbiBwcmVkaWN0aW5nIHRoZSBwcmVzZW5jZSBvZiBkaWFiZXRpYyByZXRpbm9wYXRoeSBpbiBwYXRpZW50cy4gVGhpcyBwYXBlciBkZW1vbnN0cmF0ZXMgaG93IHVuY2VydGFpbnR5IGVzdGltYXRlcyBjYW4gcHJvdmlkZSB1c2VmdWwgaW5mb3JtYXRpb24gdG8gdGhlIGNsaW5pY2lhbiB0YXNrZWQgd2l0aCBpbnRlcnByZXRpbmcgdGhlIHJlc3VsdHMgb2YgbWVkaWNhbCBpbWFnZXMuIFRoZSBrZXkgaWRlYSBpcyB0aGF0IHRoaXMgaHVtYW4tbWFjaGluZSBpbnRlcmFjdGlvbiB3aWxsIGxlYWQgdG8gb3ZlcmFsbCBiZXR0ZXIgcmVzdWx0cyB0aGFuIGVpdGhlciBjb3VsZCBwcm9kdWNlIGluZGl2aWR1YWxseS4KCkluIHRoaXMgbm90ZWJvb2sgSSB3aWxsIGZpcnN0IGJyaWVmbHkgaW50cm9kdWNlIHRoZSBnZW5lcmFsIGlkZWEgYmVoaW5kIE1DIGRyb3BvdXQuIFRoZW4gd2Ugd2lsbCBhcHBseSBNQyBkcm9wb3V0IGluIGEgY2xhc3NpZmljYXRpb24gdGFzayBiYXNlZCBvbiB0aGUgY29sbGVjdGlvbiBvZiBsYWJlbGxlZCBpbWFnZXMgaW4gdGhlIFtDSUZBUi0xMCBkYXRhIHNldF0oaHR0cHM6Ly93d3cuY3MudG9yb250by5lZHUvfmtyaXovY2lmYXIuaHRtbCkuCgojIyBCYWNrZ3JvdW5kCgpBIG5ldXJhbCBuZXR3b3JrIGlzIG1hZGUgdXAgb2YgbWFueSBuZXVyb25zIHdoaWNoIGFyZSBvcmdhbml6ZWQgaW4gbGF5ZXJzLiBOZXVyb25zIGFyZSBvZnRlbiBjYWxsZWQgbm9kZXMgb3IgdW5pdHMsIGRlcGVuZGluZyBvbiB5b3VyIGNob2ljZSBvZiBtYWNoaW5lIGxlYXJuaW5nIGxpdGVyYXR1cmUuIEhlbmNlZm9ydGggd2Ugd2lsbCByZWZlciB0byB0aGVtIGFzIHVuaXRzLiAKCkluIGEgdHlwaWNhbCBuZXVyYWwgbmV0d29yayB0aGVyZSBhcmUgbWFueSwgbWFueSB1bml0cy4gQXMgYSBjb25zZXF1ZW5jZSB0aGUgbnVtYmVyIG9mIHBhcmFtZXRlcnMgZ3JlYXRseSBleGNlZWRzIHRoZSBudW1iZXIgb2YgZGF0YSBwb2ludHMsIGRyYW1hdGljYWxseSBpbmNyZWFzaW5nIHRoZSByaXNrIG9mIG92ZXJmaXR0aW5nIChpLmUuIHRoZXJlIGFyZSBtYW55IHNldHRpbmdzIG9mIHdlaWdodHMgd2hpY2ggd2lsbCBjYXVzZSB0aGUgbmV0d29yayB0byBmaXQgdGhlIGRhdGEgYWxtb3N0IHBlcmZlY3RseSkuCgpEcm9wb3V0IGlzIGEgY29tbW9uIHN0b2NoYXN0aWMgcmVndWxhcmlzYXRpb24gdGVjaG5pcXVlIFs1XSB0aGF0IGlzIHVzZWQgdG8gcHJldmVudCBvdmVyZml0dGluZyBpbiBuZXVyYWwgbmV0d29ya3MuIFRoZSB0ZXJtIGRyb3BvdXQgcmVmZXJzIHRvIHJhbmRvbWx5ICJkcm9wcGluZyBvdXQiIG5vbm91dHB1dCB1bml0cywgdGVtcG9yYXJpbHkgcmVtb3ZpbmcgYWxsIGNvbm5lY3Rpb25zIHRvIHRoZSByZXN0IG9mIHRoZSBuZXR3b3JrLiBUaGUgbWFpbiBpZGVhIGlzIHRoYXQgaWYgdGhlIHByZXNlbmNlIG9mIG90aGVyIHVuaXRzIGlzIHVucmVsaWFibGUsIGVhY2ggdW5pdCBpcyBmb3JjZWQgdG8gbGVhcm4gaG93IHRvIGJlIHVzZWZ1bCBvbiBpdHMgb3duLiBBdCB0ZXN0IHRpbWUgYWxsIHVuaXRzIGFyZSBhY3RpdmF0ZWQsIGFuZCB0aGUgd2VpZ2h0cyBvZiB0aGUgbmV0d29yayBhcmUgc2NhbGVkIGJ5IHRoZSBkcm9wb3V0IHJhdGUgaW4gb3JkZXIgdG8gbWF0Y2ggdGhlIGV4cGVjdGVkIG91dHB1dCBkdXJpbmcgdHJhaW5pbmcuCgpSZWNlbnQgd29yayBieSBHYWwgZXQuIGFsLiBbMSwgMiwgM10gY2FzdHMgZHJvcG91dCBuZXVyYWwgbmV0d29ya3MgYXMgYXBwcm94aW1hdGUgQmF5ZXNpYW4gaW5mZXJlbmNlLiBUaGVpciByZXN1bHRzIHNob3cgdGhhdCB0aGUgcHJlZGljdGl2ZSBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uIG9mIGEgbmV1cmFsIG5ldHdvcmsgY2FuIGJlIGFwcHJveGltYXRlZCBieSBsZWF2aW5nIGRyb3BvdXQgb24gYXQgdGVzdCB0aW1lLiAKCkNvbnNpZGVyIGEgY2xhc3NpZmljYXRpb24gc2V0dGluZywgc3VjaCBhcyBpbiBDSUZBUi0xMC4gRXNzZW50aWFsbHksIHdoYXQgaGFwcGVucyB3aGVuIGFwcGx5aW5nIE1DIGRyb3BvdXQgaXMgdGhlIGZvbGxvd2luZzoKCiogQW4gaW1hZ2UgaXMgZmVkIGZvcndhcmQgdGhyb3VnaCB0aGUgbmV0d29yayAkVCQgdGltZXMgKHdlIHVzZSAkVD0xMDAkIGFzIHJlY29tbWVuZGVkIGluIFs0XSkuIEVhY2ggdGltZSB0aGUgaW1hZ2UgaXMgZmVkIHRocm91Z2ggaXMgY2FsbGVkIGEgKnN0b2NoYXN0aWMgZm9yd2FyZCBwYXNzKi4KKiBGb3IgZWFjaCBzdG9jaGFzdGljIGZvcndhcmQgcGFzcywgYSBzbGlnaHRseSBkaWZmZXJlbnQgbmV0d29yayBpcyBtYWtpbmcgcHJlZGljdGlvbnMgYmVjYXVzZSBkcm9wb3V0IGhhcyByYW5kb21seSBzd2l0Y2hlZCBvZmYgdW5pdHMuIAoqIEFzIGEgcmVzdWx0IGVhY2ggc3RvY2hhc3RpYyBmb3J3YXJkIHBhc3MgcmV0dXJucyAxMDAgc2xpZ2h0bHkgZGlmZmVyZW50IHZlY3RvcnMgb2YgY2xhc3MgcHJlZGljdGlvbnMuCiogVG8gbWFrZSBhIHByZWRpY3Rpb24gd2UgYXZlcmFnZSB0aGUgMTAwIHZlY3RvcnMuIFRoZSBjbGFzcyBjb3JyZXNwb25kaW5nIHRvIHRoZSBsYXJnZXN0IGVsZW1lbnQgaW4gdGhlIHJlc3VsdGluZyB2ZWN0b3IgaXMgb3VyIGZpbmFsIHByZWRpY3Rpb24uCiogRmluYWxseSB3ZSBjYWxjdWxhdGUgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBpbiBjbGFzcyBwcmVkaWN0aW9ucyBvdmVyIGFsbCBmb3J3YXJkIHBhc3Nlcy4gVGhpcyBpcyBvdXIgZXN0aW1hdGVkIHVuY2VydGFpbnR5LgoKTWF0aGVtYXRpY2FsbHksIG1vZGVsIHVuY2VydGFpbnkgaXMgYXBwcm94aW1hdGVkIHRoZSBlbXBpcmljYWwgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBwcmVkaWN0aW9ucyBmb3IgY2xhc3MgJGskLCBpLmUuICQkXGhhdHtcc2lnbWF9X2sgPSBcc3FydHtcZnJhY3tcc3VtX3t0PTF9XlR7W3BfdChrfFgsdykgLSBcaGF0e1xtdX1fa31dXjJ9e1QtMX19JCQgd2hlcmUgJCRcaGF0e1xtdX1fayA9IFxmcmFjezF9e1R9XHN1bV97dD0xfV5UIHBfdChrfFgsdykkJCBpcyB0aGUgYXZlcmFnZWQgc29mdG1heCBvdXRwdXRzIG9mIHRoZSBwcmVkaWN0ZWQgY2xhc3MuCgpHYWwgZXQuIGFsLiBbM10gc2hvdyB0aGF0IHRoZSBhYm92ZSBhbW91bnRzIHRvIGRyYXdpbmcgTW9udGUgQ2FybG8gc2FtcGxlcyBmcm9tIHRoZSBwcmVkaWN0aXZlIHBvc3Rlcmlvci4gVGhlaXIgd29yayBkZW1vbnN0cmF0ZXMgdGhhdCBhcHBseWluZyBkcm9wb3V0IGlzIGVmZmVjdGl2ZWx5IHRoZSBzYW1lIGFzIGRlZmluaW5nIGEgKkJheWVzaWFuIG5ldXJhbCBuZXR3b3JrKiB3aXRoIGEgQmVybm91bGxpIGFwcHJveGltYXRpbmcgcHJpb3Igb3ZlciB0aGUgcGFyYW1ldGVycy4gW0dhbCBoYXMgd3JpdHRlbiBhbiBleGNlbGxlbnQgYmxvZyBwb3N0IHRoYXQgaW50cm9kdWNlcyB0aGUgd29ya10oaHR0cDovL21sZy5lbmcuY2FtLmFjLnVrL3lhcmluL2Jsb2dfM2Q4MDFhYTUzMmMxY2UuaHRtbCkgYW5kIHRoZSBkZXJpdmF0aW9uLgoKIyMgUHJvYmxlbSBzdGF0ZW1lbnQKClRoZSBkZXJpdmF0aW9uIGluIEdhbCBldC4gYWwuIFsyXSBpcyBiYXNlZCBvbiB0aGUgb2JzZXJ2YXRpb24gdGhhdCBhIHNoYWxsb3cgbmV1cmFsIG5ldHdvcmsgd2l0aCBpbmZpbml0ZWx5IG1hbnkgd2VpZ2h0cyBjb252ZXJnZXMgdG8gYSBHYXVzc2lhbiBwcm9jZXNzIChHUCkgd2l0aCBhIHNwZWNpZmljIGNvdmFyaWFuY2UgZnVuY3Rpb24uIEEgR1AgaXMgYSBub24tcGFyYW1ldHJpYywgcHJvYmFiaWxpc3RpYyBtYWNoaW5lIGxlYXJuaW5nIG1ldGhvZC4gVGhlIGlkZWEgaXMgdGhhdCB3ZSBwbGFjZSBhIHByaW9yIGRpc3RyaWJ1dGlvbiBvdmVyIHRoZSBmdW5jdGlvbiBzcGFjZSwgYW5kIGJ5IG9ic2VydmluZyBuZXcgZGF0YSBwb2ludHMgd2UgY2FuIGZpZ3VyZSBvdXQgd2hpY2ggZnVuY3Rpb24gaXMgbW9zdCBsaWtlbHkgdG8gaGF2ZSBnZW5lcmF0ZWQgdGhlIG9ic2VydmVkIGRhdGEuIEEgR1AgaXMgZnVsbHkgc3BlY2lmaWVkIGJ5IGl0cyBtZWFuIGFuZCBjb3ZhcmlhbmNlIGZ1bmN0aW9ucywgYW5kIGFsbG93cyB1cyB0byBvYnRhaW4gdW5jZXJ0YWludHkgZXN0aW1hdGVzIFsxXS4gVGhlIGV4dGVuc2lvbiBvZiB0aGlzIHRvIGRyb3BvdXQgbmV1cmFsIG5ldHdvcmtzIGlzIHRoZSBtYWluIHJlc3VsdCBvZiBHYWwgZXQuIGFsLiBbMl0uCgpJbiBbMV0gaG93ZXZlciwgR2FsIGV0LiBhbC4gZXh0ZW5kIHRoaXMgaWRlYSBmdXJ0aGVyIHRvIGluY2x1ZGUgcHJpb3JzIG92ZXIgdGhlIHdlaWdodHMgb2YgdGhlIGNvbnZvbHV0aW9uYWwgbGF5ZXJzIGluIGEgY29udm9sdXRpb25hbCBuZXVyYWwgbmV0d29yayAoQ05OKS4gQSBDTk4gaXMgYSBzcGVjaWFsaXplZCB0eXBlIG9mIG5ldXJhbCBuZXR3b3JrLCB1c2VkIHByaW1hcmlseSBhbmQgdmVyeSBlZmZlY3RpdmVseSBmb3IgaW1hZ2UgYW5hbHlzaXMuIFRoZSBhdXRob3JzIGJyaWVmbHkgc3RhdGUgdGhhdCB0aGUgR1AgaW50ZXJwcmV0YXRpb24gaXMgbG9zdCB3aGVuIHRoZSBtb2RlbCBpcyBleHRlbmRlZCB0byBDTk5zLCBidXQgdGhhdCB0aGVzZSBuZXR3b3JrcyBzdGlsbCBjYW4gYmUgIm1vZGVsbGVkIGFzIEJheWVzaWFuIi4KCkFzIGZhciBhcyB3ZSBhcmUgYXdhcmUsIHRoZXJlIGhhdmUgYmVlbiBubyBlZmZvcnRzIHRvIGRldGVybWluZSB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgYXBwcm94aW1hdGVkIHVuY2VydGFpbnR5IGFuZCBhIG5ldHdvcmsncyBhYmlsaXR5IHRvIHByZWRpY3QgY29ycmVjdGx5LiBNb3Jlb3ZlciwgdGhlIHdvcmsgZG9uZSBzbyBmYXIgc2VlbXMgdG8gZm9jdXMgb24gdGhlIHVuY2VydGFpbnR5IGFzc29jaWF0ZWQgd2l0aCB0aGUgcHJlZGljdGVkIGNsYXNzLiBXZSB3aWxsIGV4YW1pbmUgaWYgdGhlcmUgaXMgYW55IGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gdG8gYmUgZ2FpbmVkIGZyb20gZXN0YWJsaXNoaW5nIGEgY29ubmVjdGlvbiBiZXR3ZWVuIHRoZSBwcmVkaWN0aW9uIGFuZCB0aGUgKnJ1bm5lci11cCogcHJlZGljdGlvbi4gSW4gb3RoZXIgd29yZHMsIG91ciBwcm9ibGVtIHN0YXRlbWVudCBpczoKCj4gKkFyZSB0aGUgdW5jZXJ0YWludHkgYXBwcm94aW1hdGlvbnMgb2J0YWluZWQgYnkgYXBwbHlpbmcgTW9udGUgQ2FybG8gZHJvcG91dCB0byBjb252b2x1dGlvbmFsIG5ldXJhbCBuZXR3b3JrcyBhIHJlYXNvbmFibGUgbWVhc3VyZSBvZiBtb2RlbCB1bmNlcnRhaW50eT8qCgojIyBFeHBlcmltZW50YWwgc2V0dXB7LnRhYnNldH0KClN0YXRlLW9mLXRoZS1hcnQgYXJjaGl0ZWN0dXJlcyBzdWNoIGFzIFJlc05ldCBhbmQgRGVuc2VOZXQgYXJlIHZlcnkgcG93ZXJmdWwsIGJ1dCB0aGV5IGFyZSBhbHNvIGNvbXBsaWNhdGVkIGFuZCB0aGVpciBpbm5lciB3b3JraW5ncyBhcmUgcXVpdGUgY29udm9sdXRlZC4gV2UgYXJlIHByaW1hcmlseSBpbnRlcmVzdGVkIGluIGV4YW1pbmluZyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB1bmNlcnRhaW50eSBlc3RpbWF0aW9uIGFuZCBwcmVkaWN0aXZlIGNhcGFiaWxpdGllcy4gSXQgaXMgYXJndWFibHkgYmV0dGVyIHRvIHVzZSBhIHNpbXBsZSBuZXR3b3JrIGFyY2hpdGVjdHVyZSB0byBpbGx1c3RyYXRlIHRoZSBpZGVhIG9mIE1DIGRyb3BvdXQuIEluIG91ciBhcHByb2FjaCB3ZSB1c2UgTGVOZXQtNS4gQ2xpY2sgb24gdGhlIHRhYnMgZm9yIGltcGxlbWVudGF0aW9uIGRldGFpbHMuIEZ1bGwgY29kZSBpcyBhdmFpbGFibGUgb24gW0dpdEh1Yl0obGluaykuCgojIyMgTW9kZWwKCkxlTmV0LTUgd2FzIGEgcGlvbmVlcmluZyA3LWxheWVyIGNvbnZvbHV0aW9uYWwgbmV1cmFsIG5ldHdvcmsgb3JpZ2luYWxseSBkZXZlbG9wZWQgYnkgWWFubiBMZUN1bm4gaW4gMTk5OCBmb3IgaGFuZHdyaXR0ZW4gZGlnaXQgcmVjb2duaXRpb24uIEl0IGlzIGhvcGVsZXNzbHkgcHJpbWl0aXZlIGNvbXBhcmVkIHRvIGNvbnRlbXBvcmFyeSBhcmNoaXRlY3R1cmVzLCBidXQgc3RpbGwgY2FwdHVyZXMgdGhlIGdpc3Qgb2Ygd2hhdCBhIGNvbnZvbHV0aW9uYWwgbmV0d29yayBpcyB3aGlsZSByZW1haW5pbmcgc2ltcGxlIGVub3VnaCB0byBhbGxvdyB1cyB0byB1bmRlcnN0YW5kIGV2ZXJ5IGJ1aWxkaW5nIGJsb2NrIG9mIHRoZSBuZXR3b3JrLiBUaGUgZm9sbG93aW5nIGNodW5rIHNob3dzIHRoZSBtb2RlbCBhcmNoaXRlY3R1cmU6CgpgYGB7cHl0aG9uIHB5dGhvbi5yZXRpY3VsYXRlPUZBTFNFLCBldmFsPUZBTFNFfQpjbGFzcyBsZW5ldF9hbGwobm4uTW9kdWxlKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBjb252X3NpemU9Y29udl9zaXplLCBwb29sX3NpemU9MiwgZHJvcF9yYXRlPXApOgogICAgICAgIHN1cGVyKCkuX19pbml0X18oKQogICAgICAgIHNlbGYuZHJvcF9yYXRlID0gZHJvcF9yYXRlCiAgICAgICAgc2VsZi5jb252MSA9IG5uLkNvbnYyZChpbl9jaGFubmVscz0zLCBvdXRfY2hhbm5lbHM9MTkyLCBrZXJuZWxfc2l6ZT1jb252X3NpemUpCiAgICAgICAgc2VsZi5kcm9wbWMxID0gRHJvcG91dE1DKHApCiAgICAgICAgc2VsZi5wb29sMSA9IG5uLk1heFBvb2wyZChrZXJuZWxfc2l6ZT1wb29sX3NpemUsIHN0cmlkZT0yKQogICAgICAgIHNlbGYuY29udjIgPSBubi5Db252MmQoaW5fY2hhbm5lbHM9MTkyLCBvdXRfY2hhbm5lbHM9MTkyLCBrZXJuZWxfc2l6ZT1jb252X3NpemUsIHBhZGRpbmc9MikKICAgICAgICBzZWxmLmRyb3BtYzIgPSBEcm9wb3V0TUMocCkKICAgICAgICBzZWxmLnBvb2wyID0gbm4uTWF4UG9vbDJkKGtlcm5lbF9zaXplPXBvb2xfc2l6ZSwgc3RyaWRlPTIpCiAgICAgICAgc2VsZi5kZW5zZTEgPSBubi5MaW5lYXIoaW5fZmVhdHVyZXM9Nyo3KjE5Miwgb3V0X2ZlYXR1cmVzPTEwMDApCiAgICAgICAgc2VsZi5kcm9wbWMzID0gRHJvcG91dE1DKHApCiAgICAgICAgc2VsZi5kZW5zZTIgPSBubi5MaW5lYXIoaW5fZmVhdHVyZXM9MTAwMCwgb3V0X2ZlYXR1cmVzPTEwKQogICAgICAgIAogICAgZGVmIGZvcndhcmQoc2VsZiwgeCk6CiAgICAgICAgeCA9IHNlbGYuZHJvcG1jMShzZWxmLmNvbnYxKHgpKQogICAgICAgIHggPSBzZWxmLnBvb2wxKHgpCiAgICAgICAgeCA9IHNlbGYuZHJvcG1jMihzZWxmLmNvbnYyKHgpKQogICAgICAgIHggPSBzZWxmLnBvb2wyKHgpCiAgICAgICAgeCA9IHgudmlldyh4LnNpemUoMCksIC0xKQogICAgICAgIHggPSBzZWxmLmRyb3BtYzMoRi5yZWx1KHNlbGYuZGVuc2UxKHgpKSkgICAgICAgCiAgICAgICAgeCA9IHNlbGYuZGVuc2UyKHgpCiAgICAgICAgCiAgICAgICAgcmV0dXJuIHgKYGBgCjxicj4KCiMjIyBNQyBkcm9wb3V0IGxheWVyCgpPdXIgc3BlY2lmaWNhdGlvbiBvZiBMZU5ldC01IGRpZmZlcnMgZnJvbSB0aGUgb3JnaW5pYWwgaW4gb25lIGNydWNpYWwgd2F5OiBXZSB1c2UgTW9udGUgQ2FybG8gZHJvcG91dCBsYXllcnMuIE1DIGRyb3BvdXQgaXMgbm90IGEgZmVhdHVyZSB0aGF0IGlzIGltcGxlbWVudGVkIGluIFB5VG9yY2gsIGFuZCB3ZSBtdXN0IHRoZXJlZm9yZSBpbXBsZW1lbmV0IG9uZSBvdXJzZWx2ZXMuIEZvcnR1bmF0ZWx5LCB0aGlzIGFtb3VudHMgdG8gYSBzaW1wbGUgYWRqdXN0bWVudCBvZiBleGlzdGluZyBjb2RlLiBXZSBtb2RpZnkgdGhlIGBEcm9wb3V0YCBjbGFzcyB0byB0YWtlIGFuIGFkZGl0aW9uYWwgYXJndW1lbnQgY2FsbGVkIGBkcm9wb3V0TUNgIHdpdGggZGVmYXVsdCB2YWx1ZSBzZXQgdG8gYFRydWVgOgoKYGBge3B5dGhvbiBweXRob24ucmV0aWN1bGF0ZT1GQUxTRSwgZXZhbD1GQUxTRX0KY2xhc3MgRHJvcG91dE1DKG5uLk1vZHVsZSk6CiAgICByIiIiCiAgICBNb2RpZmllZCB2ZXJzaW9uIG9mIERyb3BvdXQgZnJvbSB0b3JjaC9ubi9tb2R1bGVzL2Ryb3BvdXQucHkKICAgIEFyZ3M6CiAgICAgICAgcDogcHJvYmFiaWxpdHkgb2YgYW4gZWxlbWVudCB0byBiZSB6ZXJvZWQuIERlZmF1bHQ6IDAuNQogICAgICAgIGRyb3BvdXRNQzogSWYgc2V0IHRvIGBgVHJ1ZWBgLCBkcm9wb3V0IGlzIHR1cm5lZCBvbiBhdCB0ZXN0IHRpbWUuIERlZmF1bHQ6IGBgVHJ1ZWAKICAgICAgICBpbnBsYWNlOiBJZiBzZXQgdG8gYGBUcnVlYGAsIHdpbGwgZG8gdGhpcyBvcGVyYXRpb24gaW4tcGxhY2UuIERlZmF1bHQ6IGBgRmFsc2VgYAogICAgU2hhcGU6CiAgICAgICAgLSBJbnB1dDogYEFueWAuIElucHV0IGNhbiBiZSBvZiBhbnkgc2hhcGUKICAgICAgICAtIE91dHB1dDogYFNhbWVgLiBPdXRwdXQgaXMgb2YgdGhlIHNhbWUgc2hhcGUgYXMgaW5wdXQKICAgIEV4YW1wbGVzOjoKICAgICAgICA+Pj4gbSA9IG5uLkRyb3BvdXQocD0wLjIpCiAgICAgICAgPj4+IGlucHV0ID0gYXV0b2dyYWQuVmFyaWFibGUodG9yY2gucmFuZG4oMjAsIDE2KSkKICAgICAgICA+Pj4gb3V0cHV0ID0gbShpbnB1dCkKICAgIC4uIF9JbXByb3ZpbmcgbmV1cmFsIG5ldHdvcmtzIGJ5IHByZXZlbnRpbmcgY28tYWRhcHRhdGlvbiBvZiBmZWF0dXJlCiAgICAgICAgZGV0ZWN0b3JzOiBodHRwczovL2FyeGl2Lm9yZy9hYnMvMTIwNy4wNTgwCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgcD0wLjUsIGRyb3BvdXRNQz1UcnVlLCBpbnBsYWNlPUZhbHNlKToKICAgICAgICBzdXBlcihEcm9wb3V0TUMsIHNlbGYpLl9faW5pdF9fKCkKICAgICAgICBpZiBwIDwgMCBvciBwID4gMToKICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigiZHJvcG91dCBwcm9iYWJpbGl0eSBoYXMgdG8gYmUgYmV0d2VlbiAwIGFuZCAxLCAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImJ1dCBnb3Qge30iLmZvcm1hdChwKSkKICAgICAgICBzZWxmLnAgPSBwCiAgICAgICAgc2VsZi5kcm9wb3V0TUMgPSBkcm9wb3V0TUMKICAgICAgICBzZWxmLmlucGxhY2UgPSBpbnBsYWNlCgogICAgZGVmIGZvcndhcmQoc2VsZiwgaW5wdXQpOgogICAgICAgIHJldHVybiBGLmRyb3BvdXQoaW5wdXQsIHNlbGYucCwgc2VsZi5kcm9wb3V0TUMsIHNlbGYuaW5wbGFjZSkKCiAgICBkZWYgX19yZXByX18oc2VsZik6CiAgICAgICAgaW5wbGFjZV9zdHIgPSAnLCBpbnBsYWNlJyBpZiBzZWxmLmlucGxhY2UgZWxzZSAnJwogICAgICAgIHJldHVybiBzZWxmLl9fY2xhc3NfXy5fX25hbWVfXyArICcoJyBcCiAgICAgICAgICAgICsgJ3A9JyArIHN0cihzZWxmLnApIFwKICAgICAgICAgICAgKyBpbnBsYWNlX3N0ciArICcpJwpgYGAKPGJyPgoKIyMjIEluZmVyZW5jZQoKV2UgbmVlZCB0byBkZWZpbmUgYSBmdW5jdGlvbiB0aGF0IHBlcmZvcm1zIGluZmVyZW5jZSBvdmVyIG91dCBpbnB1dC4gVGhlIGZvbGxvd2luZyBjaHVuayBjb250YWlucyB0aGUgcmVsZXZhbnQgY29kZSBmb3IgdGhlIHNhbXBsaW5nIHByb2NlZHVyZSBkZXNjcmliZWQgaW4gc2VjdGlvbiAxLjEuIFRoZSBmdW5jdGlvbiBgaW5mZXJlbmNlYCBzdG9yZXMgYWxsIHRoZSByZWxldmFudCBzdGF0aXN0aWNzIGFuZCBzb2Z0bWF4IGRpc3RyaWJ1dGlvbnMgaW4gYSBkaWN0aW9uYXJ5IG5hbWVkIGBvdXRwdXRgLiBUaGUgcmVzdWx0cyBhcmUgdGhlbiB0dXJuZWQgaW50byBhIHBhbmRhcyBkYXRhZnJhbWUgYW5kIHNvbWUgdmVyeSBiYXNpYyBmZWF0dXJlIGVuZ2luZWVyaW5nIGlzIHBlcmZvcm1lZC4gRmluYWxseSwgdGhlIGRhdGEgaXMgcHJlcGFyZWQgZm9yIHN0YXRpc3RpY2FsIGFuYWx5c2lzIGluIFIuCgpgYGB7cHl0aG9uIHB5dGhvbi5yZXRpY3VsYXRlPUZBTFNFLCBldmFsPUZBTFNFfQpkZWYgaW5mZXJlbmNlKGxlYXJuZXIsIGRhdGEsIFQ9MTAwKToKICAgICcnJyBGdW5jdGlvbiB0aGF0IGdhdGhlcnMgYWxsIHJlbGV2YW50IG51bWVyaWNhbCByZXN1bHRzIGZyb20gTUMgZHJvcG91dCBvdmVyIFQgaXRlcmF0aW9ucy4KICAgICAgICAKICAgICAgICBBcmd1bWVudHM6CiAgICAgICAgbGVhcm5lciwgZmFzdGFpIGxlYXJuZXIgb2JqZWN0CiAgICAgICAgZGF0YSwgZmFzdGFpIGRhdGFsb2FkZXIKICAgICAgICBULCBudW1iZXIgb2Ygc3RvY2hhc3RpYyBmb3J3YXJkIHBhc3NlcwogICAgJycnCiAgICAjIEdldCBpbWFnZXMsIGxhYmVscyBhbmQgZmlsZW5hbWVzCiAgICBpbWdzLCBsYWJlbHMgPSBuZXh0KGl0ZXIoZGF0YS52YWxfZGwpKQogICAgZm5hbWVzID0gZGF0YS52YWxfZHMuZm5hbWVzCgogICAgIyBFbXB0eSBkaWN0aW9uYXJ5IHRvIHN0b3JlIGFsbCBvdXRwdXQKICAgIG91dHB1dCA9IHt9CgogICAgIyBFbXB0eSBhcnJheSB0byBzdG9yZSByZXN1bHRzIGluCiAgICByZXN1bHRzID0gbnAuZW1wdHkoKFQsIG51bV9jbGFzc2VzKSkKCiAgICAjIGl0ZXJhdG9yIGluZGV4IHRvIGtlZXAgaW4gZGljdGlvbmFyeQogICAgaz0wCgogICAgZm9yIChpbWcsIGxhYmVsLCBmbmFtZSkgaW4gbGlzdCh6aXAoaW1ncywgbGFiZWxzLCBmbmFtZXMpKToKCiAgICAgICAgZm9yIGkgaW4gcmFuZ2UoVCk6CiAgICAgICAgICAgIHByZWRpY3Rpb24gPSBsZWFybmVyLnByZWRpY3RfYXJyYXkoaW1nW05vbmVdKQogICAgICAgICAgICByZXN1bHRzW2ldID0gcHJlZGljdGlvbgoKICAgICAgICBwcm9icyA9IHRvX25wKEYuc29mdG1heChWKHJlc3VsdHMpKSkKICAgICAgICBwcm9ic19tZWFuID0gbnAubWVhbihwcm9icywgYXhpcz0wKQogICAgICAgIHByZWRfc3RkID0gbnAuc3RkKHByb2JzLCBheGlzPTApCgogICAgICAgIHByZWRpY3Rpb24gPSBwcm9ic19tZWFuLmFyZ21heCgpCiAgICAgICAgdW5jZXJ0YWludHkgPSBwcmVkX3N0ZFtwcmVkaWN0aW9uXQoKICAgICAgICBjb3JyZWN0ID0gMSBpZiBwcmVkaWN0aW9uID09IGxhYmVsIGVsc2UgMAoKICAgICAgICBvdXRwdXRba10gPSB7ImltZyI6IGZuYW1lLCAic29mdG1heF9kaXN0IjogcHJvYnMsICJwcm9icyI6IHByb2JzX21lYW4sICJwcmVkaWN0aW9uIjogcHJlZGljdGlvbiwgInRydXRoIjogbGFiZWwsICJ1bmNlcnRhaW50eSI6IHVuY2VydGFpbnR5LCAiY29ycmVjdCI6IGNvcnJlY3R9CiAgICAgICAgays9MQogICAgCiAgICByZXR1cm4gb3V0cHV0CmBgYAoKIyMgTW9kZWxzCgpXZSB3aWxsIGV4YW1pbmUgZGF0YSBnYXRoZXJlZCBmcm9tIGZvdXIgdmFyaWFudHMgb2YgTGVOZXQtNS4gQWxsIG1vZGVscyB3ZXJlIHRyYWluZWQgb24gQ0lGQVItMTAgdXNpbmcgdGhlIGBmYXN0YWlgIEFQSS4gQ0lGQVItMTAgY29udGFpbnMgNjAuMDAwIGxhYmVsbGVkIDMyeDMyeDMgY29sb3IgaW1hZ2VzIGJlbG9uZ2luZyB0byAxMCBkaWZmZXJlbnQgY2xhc3Nlcy4gVGhlIGlucHV0IGRhdGEgd2FzIHNwbGl0IGludG8gYSB0cmFpbmluZyBzZXQgb2YgNTAuMDAwIGltYWdlcyBhbmQgYSB0ZXN0IHNldCBvZiAxMC4wMDAgaW1hZ2VzLiBUaGUgdHJhaW5pbmcgc2V0IHdhcyBmdXJ0aGVyIHNwbGl0IGludG8gYSB0cmFpbmluZyBzZXQgYW5kIGEgdmFsaWRhdGlvbiBzZXQuIEFsbCBtb2RlbHMgaGF2ZSBgd2VpZ2h0X2RlY2F5ID0gMC4wMDA1YCBhbmQgYWxsIGxlYXJuaW5nIHJhdGVzIHdlcmUgY2hvc2VuIHVzaW5nIGBscl9maW5kYDoKCiogYG1vZGVsNTVgOiBUcmFpbmVkIGZvciA2MCBlcG9jaHMgd2l0aCBhIGxlYXJuaW5nIHJhdGUgb2YgMC4wMDEgYW5kIGBrZXJuZWwgc2l6ZSA9ICg1LDUpYCBhbmQgYGRyb3BfcmF0ZSA9IC41YC4gICoqMC43MTI4MCB2YWxpZGF0aW9uIGxvc3MqKiBhdCBlbmQgb2YgdHJhaW5pbmcgd2l0aCBhbiAqKmFjY3VyYWN5IG9mIDAuNzYxMzcqKiBvbiB0aGUgdmFsaWRhdGlvbiBkYXRhLiBgbW9kZWw1NWAgcmVwcmVzZW50cyB0aGUgYmFzZWxpbmUgaW1wbGVtZW50YXRpb24gb2YgTGVOZXQtNS4gSXQgaXMgaWRlbnRpY2FsIGluIHN0cnVjdHVyZSB0byB0aGUgb25lIHVzZWQgYnkgR2FsIGV0LiBhbC4gWzFdLgoKKiBgbW9kZWw1MmA6IFRyYWluZWQgZm9yIDE0IGVwb2NocyB3aXRoIGEgbGVhcm5pbmcgcmF0ZSBvZiAwLjAxIGZvciB0aGUgZmlyc3QgNyBhbmQgMC4wMDAxIG9uIHRoZSByZW1haW5pbmcuIENoYW5nZWQgZHVlIHRvIHJhcGlkIG92ZXJmaXR0aW5nLiBNb2RlbCBoYXMgYGtlcm5lbCBzaXplID0gKDUsNSlgIGFuZCBgZHJvcF9yYXRlID0gLjJgLiAqKjAuNzQxODYgdmFsaWRhdGlvbiBsb3NzKiogYXQgZW5kIG9mIHRyYWluaW5nIHdpdGggYW4gKiphY2N1cmFjeSBvZiAwLjc0NDA2Kiogb24gdGhlIHZhbGlkYXRpb24gZGF0YS4KCiogYG1vZGVsMzVgOiBUcmFpbmVkIGZvciA2NiBlcG9jaHMgd2l0aCBhIGxlYXJuaW5nIHJhdGUgb2YgMC4wMDEgYW5kIGBrZXJuZWwgc2l6ZSA9ICgzLDMpYCBhbmQgYGRyb3BfcmF0ZSA9IC41YC4gKiowLjc1NDgzIHZhbGlkYXRpb24gbG9zcyoqIGF0IGVuZCBvZiB0cmFpbmluZyB3aXRoIGFuICoqYWNjdXJhY3kgb2YgMC43NDIxOCoqIG9uIHRoZSB2YWxpZGF0aW9uIGRhdGEuIAoKKiBgbW9kZWwzMmA6IFRyYWluZWQgZm9yIDUyIGVwb2NocyB3aXRoIGEgbGVhcm5pbmcgcmF0ZSBvZiAwLjAwMSBhbmQgYGtlcm5lbCBzaXplID0gKDMsMylgIGFuZCBgZHJvcF9yYXRlID0gLjJgLiAqKjAuNzU0NDkgdmFsaWRhdGlvbiBsb3NzKiogYXQgZW5kIG9mIHRyYWluaW5nIHdpdGggYW4gKiphY2N1cmFjeSBvZiAwLjc0MDkwKiogb24gdGhlIHZhbGlkYXRpb24gZGF0YS4KCk5vdGUgdGhhdCBgbW9kZWw1NWAgaGFzIGEgc2xpZ2h0bHkgYmV0dGVyIGJhc2VsaW5lIHBlcmZvcm1hbmNlIHRoYW4gdGhlIG90aGVyIG1vZGVscy4KCgojIyBEYXRhCgpUaGUgZGF0YSBjb250YWlucyB0aGUgZm9sbG93aW5nIHZhcmlhYmxlcyBhZnRlciBpdCBoYXMgYmVlbiBwcmVwYXJlZCBmb3IgYW5hbHlzaXMgaW4gUjoKCiogYGNvcnJlY3RgIChsb2dpY2FsKTogaW5kaWNhdG9yIHRoZSBpcyBgVFJVRWAgaWYgdGhlIHByZWRpY3RlZCBjbGFzcyBsYWJlbCBtYXRjaGVzIHRoZSB0cnVlIGNsYXNzIGxhYmVsLCBlbHNlIGBGQUxTRWAuCgoqIGBwcmVkaWN0aW9uYCAoaW50KTogcHJlZGljdGVkIGNsYXNzIGxhYmVsLgoKKiBgdHJ1dGhgIChpbnQpOiB0cnVlIGNsYXNzIGxhYmVsLgoKKiBgdW5jZXJ0YWludHlgIChkYmwpOiBlbXBpcmljYWwgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHNvZnRtYXggdmFsdWVzIGZvciBwcmVkaWN0ZWQgY2xhc3MuCgoqIGBwcm9iMWAgKGRibCk6IGFyZ21heCBvZiBtZWFuIHNvZnRtYXggb3V0cHV0LCBpLmUuIG1lYW4gcHJvYmFiaWxpdHkgb2YgcHJlZGljdGVkIGNsYXNzLgoKKiBgcHJvYjJgIChkYmwpOiBtZWFuIHByb2JhYmlsaXR5IG9mIHJ1bm5lci11cCBwcmVkaWN0aW9uLgoKKiBgY2xhc3MyYCAoaW50KTogY2xhc3MgbGFiZWwgb2YgcnVubmVyLXVwIHByZWRpY3Rpb24uCgoqIGBsb2dpdF9wcm9iMWAgKGRibCk6IGxvZ2l0IHRyYW5zZm9ybWF0aW9uIG9mIGBwcm9iMWAuCgoqIGBkaWZmYCAoZGJsKTogYHByb2IxYC1gcHJvYjJgCgoqIGBkaWZmX3NkX3JhdGlvYCAoZGJsKTogYGRpZmYvdW5jZXJ0YWludHlgLgoKQWxsIHRoZSB2YXJpYWJsZXMgYWJvdmUgYXJlIHByZXR0eSBzdGFuZGFyZCwgd2l0aCB0aGUgZXhjZXB0aW9uIG9mIGBkaWZmX3NkX3JhdGlvYC4gSW50dWl0aXZlbHksIGlmIGBkaWZmYCBpcyBsYXJnZSwgdGhlIGF2ZXJhZ2VkIG1vZGVscyBhbGwgYWdyZWUgdGhhdCBjbGFzcyAkayQgaXMgdGhlIGNvcnJlY3QgcHJlZGljdGlvbi4gSWYgYGRpZmZgIGlzIHNtYWxsLCB0aGUgbW9kZWxzIHNhbXBsZWQgYnkgTUMgZHJvcG91dCBkb24ndCBhZ3JlZSBvbiBhIHNpbmdsZSBjbGFzcy4gVGh1cyBgZGlmZmAgYWxzbyBzZXJ2ZXMgYXMgYSBwcm94eSBmb3IgdW5jZXJ0YWludHkuIE1vZGVsIGB1bmNlcnRhaW50eWAsIGhvd2V2ZXIsIGlzIGFwcHJveGltYXRlZCBieSB0aGUgZW1waXJpY2FsIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgcHJlZGljdGlvbnMgZm9yIGNsYXNzICRrJC4gVGh1cyBgZGlmZl9zZF9yYXRpb2AgaXMgZXhwcmVzc2VkIGJ5ICQkXHRhdV97a2p9ID0gXGZyYWN7XGhhdHtcbXV9X2sgLSBcaGF0e1xtdX1fan17XGhhdHtcc2lnbWF9X2t9JCQgd2hlcmUgJGokIGlzIHRoZSBydW5uZXItdXAgcHJlZGljdGlvbi4gJFx0YXVfe2prfSQgZ2l2ZXMgdXMgYSByYXRpbyBvZiB0d28gZGlmZmVyZW50IG1lYXN1cmVzIG9mIHVuY2VydGFpbnR5LgoKIyBFeHBsb3JhdG9yeSBhbmFseXNpcwoKVGhpcyBzZWN0aW9uIHdpbGwgYmUgZGl2aWRlZCBpbnRvIHRvIHBhcnRzLiBGaXJzdCwgd2Ugd2lsbCBleGFtaW5lIHRoZSByZXN1bHRpbmcgZGF0YSBmcm9tIGBtb2RlbDU1YCwgd2hpY2ggd2lsbCBiZSByZWdhcmRlZCBhcyBvdXIgYmFzZWxpbmUgbW9kZWwuIE5leHQsIHdlIHdpbGwgYW5hbHlzZSB0aGUgZGF0YSBmcm9tIG1vZGVscyBvYnRhaW5lZCBieSB2YXJ5aW5nIGtlcm5lbCBzaXplcyBhbmQgZHJvcG91dCByYXRlcy4gIAoKIyMgVW5jZXJ0YWludHkgYW5hbHlzaXMgb2YgYmFzZWxpbmUgbW9kZWwgey50YWJzZXR9CgojIyMgRW50aXJlIGRhdGEgc2V0CgpgYGB7cn0KIyBJbXBvcnRpbmcgZGF0YQpkYXRhIDwtIGFzLnRpYmJsZShyZWFkLmNzdigifi9Eb2N1bWVudHMvTWFzdGVyb3BwZ2F2ZS9EYXRhL1Jlc3VsdGF0ZXIvbGVuZXQtbW9kZWw1NS5jc3YiKSkKZGYgPC0gc2VsZWN0KGRhdGEsIC1YKQoKIyBTdW1tYXJpemluZyBlbnRpcmUgZGF0YSBzZXQKc3VtbWFyeShkZikKYGBgCgpGb3IgdGhlIGVudGlyZSBzZXQgb2YgY2xhc3NpZmljYXRpb25zLCB3ZSBoYXZlIHRoZSBmb2xsb3dpbmcgbm90YWJsZSBxdWFudGl0aWVzOgoKKiBUaGUgbWVhbiAqKnVuY2VydGFpbnR5Kiogb2YgdGhlIHByZWRpY3RlZCBjbGFzcyBpcyBgciBtZWFuKGRmJHVuY2VydGFpbnR5KWAgYW5kIHRoZSBtZWRpYW4gaXMgYHIgbWVkaWFuKGRmJHVuY2VydGFpbnR5KWAuIFRoZSBtaW5pbXVtIHZhbHVlIGlzIGByIG1pbihkZiR1bmNlcnRhaW50eSlgLCB0aGUgbWF4aW11bSBpcyBgciBtYXgoZGYkdW5jZXJ0YWludHkpYC4gVGhlIGludGVycXVhcnRpbGUgcmFuZ2UgKElRUikgaXMgYHIgSVFSKGRmJHVuY2VydGFpbnR5KWAuCgoqIFRoZSBtZWFuICoqc29mdG1heCBvdXRwdXQgb2YgdGhlIHByZWRpY3RlZCBjbGFzcyoqIGlzIGByIG1lYW4oZGYkcHJvYjEpYCBhbmQgdGhlIG1lZGlhbiBpcyBgciBtZWRpYW4oZGYkcHJvYjEpYC4gVGhlIG1pbmltdW0gdmFsdWUgaXMgYHIgbWluKGRmJHByb2IxKWAsIHRoZSBtYXhpbXVtIGlzIGByIG1heChkZiRwcm9iMSlgLiBUaGUgSVFSIGlzIGByIElRUihkZiRwcm9iMSlgLgoKKiBUaGUgbWVhbiAqKnNvZnRtYXggb3V0cHV0IG9mIHRoZSBydW5uZXItdXAqKiBpcyBgciBtZWFuKGRmJHByb2IyKWAgYW5kIHRoZSBtZWRpYW4gaXMgYHIgbWVkaWFuKGRmJHByb2IyKWAuIFRoZSBtaW5pbXVtIHZhbHVlIGlzIGByIG1pbihkZiRwcm9iMilgLCB0aGUgbWF4aW11bSBpcyBgciBtYXgoZGYkcHJvYjIpYC4gVGhlIElRUiBpcyBgciBJUVIoZGYkcHJvYjIpYC4KCiogVGhlIG1lYW4gKipkaWZmZXJlbmNlKiogYmV0d2VlbiB0aGUgc29mdG1heCBvdXB1dHMgb2YgdGhlIHByZWRpY3Rpb24gYW5kIHJ1bm5lci11cCBpcyBgciBtZWFuKGRmJGRpZmYpYCBhbmQgdGhlIG1lZGlhbiBpcyBgciBtZWRpYW4oZGYkZGlmZilgLiBUaGUgbWluaW11bSB2YWx1ZSBpcyBgciBtaW4oZGYkZGlmZilgLCB0aGUgbWF4aW11bSBpcyBgciBtYXgoZGYkZGlmZilgLiBUaGUgSVFSIGlzIGByIElRUihkZiRkaWZmKWAuCgoqIFRoZSBtZWFuICoqZGlmZmVyZW5jZSB0byB1bmNlcnRhaW50eSByYXRpbyoqLCBvciAkXHRhdV97amt9JCwgaXMgYHIgbWVhbihkZiRkaWZmX3NkX3JhdGlvKWAgYW5kIHRoZSBtZWRpYW4gaXMgYHIgbWVkaWFuKGRmJGRpZmZfc2RfcmF0aW8pYC4gVGhlIG1pbmltdW0gdmFsdWUgaXMgYHIgbWluKGRmJGRpZmZfc2RfcmF0aW8pYCwgdGhlIG1heGltdW0gaXMgYHIgbWF4KGRmJGRpZmZfc2RfcmF0aW8pYC4gVGhlIElRUiBpcyBgciBJUVIoZGYkZGlmZl9zZF9yYXRpbylgLgoKIyMjIFN1bW1hcnkgc3RhdGlzdGljcwoKYGBge3J9CiMgQWdncmVnYXRpbmcgc3VtbWFyeSBzdGF0aXN0aWNzIGJ5IGNvcnJlY3QvaW5jb3JyZWN0CmFnZ19kZiA8LSBkZiAlPiUgCiAgZ3JvdXBfYnkoY29ycmVjdCkgJT4lIAogIHN1bW1hcmlzZShuPW4oKSwKICAgICAgICAgIG1lYW5fdW5jZXJ0YWludHk9bWVhbih1bmNlcnRhaW50eSksIAogICAgICAgICAgc2RfdW5jZXJ0YWludHk9c2QodW5jZXJ0YWludHkpLCAKICAgICAgICAgIG1lYW5fZGlmZj1tZWFuKGRpZmYpLAogICAgICAgICAgc2RfZGlmZj1zZChkaWZmKSwKICAgICAgICAgIG1lYW5fcmF0aW89bWVhbihkaWZmX3NkX3JhdGlvKSwKICAgICAgICAgIHNkX3JhdGlvPXNkKGRpZmZfc2RfcmF0aW8pKQphZ2dfZGYKYGBgCgoqIFRoZSBtb2RlbCBoYXMgKippbmNvcnJlY3RseSBjbGFzc2lmaWVkKiogYHIgYWdnX2RmJG5bMV1gIGltYWdlcy4gVGhlIG1lYW4gdW5jZXJ0YWludHkgb2YgdGhlIHByZWRpY3RlZCBjbGFzcyBpcyBgciBtZWFuKGFnZ19kZiRtZWFuX3VuY2VydGFpbnR5WzFdKWAuCgoqIFRoZSBtb2RlbCBoYXMgKipjb3JyZWN0bHkgY2xhc3NpZmllZCoqIGByIGFnZ19kZiRuWzJdYCBpbWFnZXMuIFRoZSBtZWFuIGVzdGltYXRlZCB1bmNlcnRhaW50eSBpcyBgciBhZ2dfZGYkbWVhbl91bmNlcnRhaW50eVsyXWAuCgojIyMgSW5jb3JyZWN0IGNsYXNzaWZpY2F0aW9ucwoKYGBge3J9CiMgU3VtbWFyaXppbmcgaW5jb3JyZWN0IHByZWRpY3Rpb25zCmluY29ycmVjdF9kZiA8LSBkZiAlPiUgCiAgZmlsdGVyKGNvcnJlY3Q9PTApCnN1bW1hcnkoaW5jb3JyZWN0X2RmKQpgYGAKCkZvciB0aGUgZW50aXJlIHNldCBvZiAqKmluY29ycmVjdCBjbGFzc2lmaWNhdGlvbnMqKiwgd2UgaGF2ZSB0aGUgZm9sbG93aW5nIG5vdGFibGUgcXVhbnRpdGllczoKCiogVGhlIG1lYW4gKip1bmNlcnRhaW50eSoqIG9mIHRoZSBwcmVkaWN0ZWQgY2xhc3MgaXMgYHIgbWVhbihpbmNvcnJlY3RfZGYkdW5jZXJ0YWludHkpYCBhbmQgdGhlIG1lZGlhbiBpcyBgciBtZWRpYW4oaW5jb3JyZWN0X2RmJHVuY2VydGFpbnR5KWAuIFRoZSBtaW5pbXVtIHZhbHVlIGlzIGByIG1pbihpbmNvcnJlY3RfZGYkdW5jZXJ0YWludHkpYCwgdGhlIG1heGltdW0gaXMgYHIgbWF4KGluY29ycmVjdF9kZiR1bmNlcnRhaW50eSlgLiBUaGUgaW50ZXJxdWFydGlsZSByYW5nZSAoSVFSKSBpcyBgciBJUVIoaW5jb3JyZWN0X2RmJHVuY2VydGFpbnR5KWAuCgoqIFRoZSBtZWFuICoqc29mdG1heCBvdXRwdXQgb2YgdGhlIHByZWRpY3RlZCBjbGFzcyoqIGlzIGByIG1lYW4oaW5jb3JyZWN0X2RmJHByb2IxKWAgYW5kIHRoZSBtZWRpYW4gaXMgYHIgbWVkaWFuKGluY29ycmVjdF9kZiRwcm9iMSlgLiBUaGUgbWluaW11bSB2YWx1ZSBpcyBgciBtaW4oaW5jb3JyZWN0X2RmJHByb2IxKWAsIHRoZSBtYXhpbXVtIGlzIGByIG1heChpbmNvcnJlY3RfZGYkcHJvYjEpYC4gVGhlIElRUiBpcyBgciBJUVIoaW5jb3JyZWN0X2RmJHByb2IxKWAuCgoqIFRoZSBtZWFuICoqc29mdG1heCBvdXRwdXQgb2YgdGhlIHJ1bm5lci11cCoqIGlzIGByIG1lYW4oaW5jb3JyZWN0X2RmJHByb2IyKWAgYW5kIHRoZSBtZWRpYW4gaXMgYHIgbWVkaWFuKGluY29ycmVjdF9kZiRwcm9iMilgLiBUaGUgbWluaW11bSB2YWx1ZSBpcyBgciBtaW4oaW5jb3JyZWN0X2RmJHByb2IyKWAsIHRoZSBtYXhpbXVtIGlzIGByIG1heChpbmNvcnJlY3RfZGYkcHJvYjIpYC4gVGhlIElRUiBpcyBgciBJUVIoaW5jb3JyZWN0X2RmJHByb2IyKWAuCgoqIFRoZSBtZWFuICoqZGlmZmVyZW5jZSoqIGJldHdlZW4gdGhlIHNvZnRtYXggb3VwdXRzIG9mIHRoZSBwcmVkaWN0aW9uIGFuZCBydW5uZXItdXAgaXMgYHIgbWVhbihpbmNvcnJlY3RfZGYkZGlmZilgIGFuZCB0aGUgbWVkaWFuIGlzIGByIG1lZGlhbihpbmNvcnJlY3RfZGYkZGlmZilgLiBUaGUgbWluaW11bSB2YWx1ZSBpcyBgciBtaW4oaW5jb3JyZWN0X2RmJGRpZmYpYCwgdGhlIG1heGltdW0gaXMgYHIgbWF4KGluY29ycmVjdF9kZiRkaWZmKWAuIFRoZSBJUVIgaXMgYHIgSVFSKGluY29ycmVjdF9kZiRkaWZmKWAuCgoqIFRoZSBtZWFuICoqZGlmZmVyZW5jZSB0byB1bmNlcnRhaW50eSByYXRpbyoqLCBvciAkXHRhdV97amt9JCwgaXMgYHIgbWVhbihpbmNvcnJlY3RfZGYkZGlmZl9zZF9yYXRpbylgIGFuZCB0aGUgbWVkaWFuIGlzIGByIG1lZGlhbihpbmNvcnJlY3RfZGYkZGlmZl9zZF9yYXRpbylgLiBUaGUgbWluaW11bSB2YWx1ZSBpcyBgciBtaW4oaW5jb3JyZWN0X2RmJGRpZmZfc2RfcmF0aW8pYCwgdGhlIG1heGltdW0gaXMgYHIgbWF4KGluY29ycmVjdF9kZiRkaWZmX3NkX3JhdGlvKWAuIFRoZSBJUVIgaXMgYHIgSVFSKGluY29ycmVjdF9kZiRkaWZmX3NkX3JhdGlvKWAuCgojIyMgQ29ycmVjdCBjbGFzc2lmaWNhdGlvbnMKCmBgYHtyIH0KIyBTdW1tYXJpemluZyBjb3JyZWN0IHByZWRpY3Rpb25zCmNvcnJlY3RfZGYgPC0gZGYgJT4lIAogIGZpbHRlcihjb3JyZWN0PT0xKQpzdW1tYXJ5KGNvcnJlY3RfZGYpCmBgYAoKRm9yIHRoZSBlbnRpcmUgc2V0IG9mICoqY29ycmVjdCBjbGFzc2lmaWNhdGlvbnMqKiwgd2UgaGF2ZSB0aGUgZm9sbG93aW5nIG5vdGFibGUgcXVhbnRpdGllczoKCiogVGhlIG1lYW4gKip1bmNlcnRhaW50eSoqIG9mIHRoZSBwcmVkaWN0ZWQgY2xhc3MgaXMgYHIgbWVhbihjb3JyZWN0X2RmJHVuY2VydGFpbnR5KWAgYW5kIHRoZSBtZWRpYW4gaXMgYHIgbWVkaWFuKGNvcnJlY3RfZGYkdW5jZXJ0YWludHkpYC4gVGhlIG1pbmltdW0gdmFsdWUgaXMgYHIgbWluKGNvcnJlY3RfZGYkdW5jZXJ0YWludHkpYCwgdGhlIG1heGltdW0gaXMgYHIgbWF4KGNvcnJlY3RfZGYkdW5jZXJ0YWludHkpYC4gVGhlIGludGVycXVhcnRpbGUgcmFuZ2UgKElRUikgaXMgYHIgSVFSKGNvcnJlY3RfZGYkdW5jZXJ0YWludHkpYC4KCiogVGhlIG1lYW4gKipzb2Z0bWF4IG91dHB1dCBvZiB0aGUgcHJlZGljdGVkIGNsYXNzKiogaXMgYHIgbWVhbihjb3JyZWN0X2RmJHByb2IxKWAgYW5kIHRoZSBtZWRpYW4gaXMgYHIgbWVkaWFuKGNvcnJlY3RfZGYkcHJvYjEpYC4gVGhlIG1pbmltdW0gdmFsdWUgaXMgYHIgbWluKGNvcnJlY3RfZGYkcHJvYjEpYCwgdGhlIG1heGltdW0gaXMgYHIgbWF4KGNvcnJlY3RfZGYkcHJvYjEpYC4gVGhlIElRUiBpcyBgciBJUVIoY29ycmVjdF9kZiRwcm9iMSlgLgoKKiBUaGUgbWVhbiAqKnNvZnRtYXggb3V0cHV0IG9mIHRoZSBydW5uZXItdXAqKiBpcyBgciBtZWFuKGNvcnJlY3RfZGYkcHJvYjIpYCBhbmQgdGhlIG1lZGlhbiBpcyBgciBtZWRpYW4oY29ycmVjdF9kZiRwcm9iMilgLiBUaGUgbWluaW11bSB2YWx1ZSBpcyBgciBtaW4oY29ycmVjdF9kZiRwcm9iMilgLCB0aGUgbWF4aW11bSBpcyBgciBtYXgoY29ycmVjdF9kZiRwcm9iMilgLiBUaGUgSVFSIGlzIGByIElRUihjb3JyZWN0X2RmJHByb2IyKWAuCgoqIFRoZSBtZWFuICoqZGlmZmVyZW5jZSoqIGJldHdlZW4gdGhlIHNvZnRtYXggb3VwdXRzIG9mIHRoZSBwcmVkaWN0aW9uIGFuZCBydW5uZXItdXAgaXMgYHIgbWVhbihjb3JyZWN0X2RmJGRpZmYpYCBhbmQgdGhlIG1lZGlhbiBpcyBgciBtZWRpYW4oY29ycmVjdF9kZiRkaWZmKWAuIFRoZSBtaW5pbXVtIHZhbHVlIGlzIGByIG1pbihjb3JyZWN0X2RmJGRpZmYpYCwgdGhlIG1heGltdW0gaXMgYHIgbWF4KGNvcnJlY3RfZGYkZGlmZilgLiBUaGUgSVFSIGlzIGByIElRUihjb3JyZWN0X2RmJGRpZmYpYC4KCiogVGhlIG1lYW4gKipkaWZmZXJlbmNlIHRvIHVuY2VydGFpbnR5IHJhdGlvKiosIG9yICRcdGF1X3tqa30kLCBpcyBgciBtZWFuKGNvcnJlY3RfZGYkZGlmZl9zZF9yYXRpbylgIGFuZCB0aGUgbWVkaWFuIGlzIGByIG1lZGlhbihjb3JyZWN0X2RmJGRpZmZfc2RfcmF0aW8pYC4gVGhlIG1pbmltdW0gdmFsdWUgaXMgYHIgbWluKGNvcnJlY3RfZGYkZGlmZl9zZF9yYXRpbylgLCB0aGUgbWF4aW11bSBpcyBgciBtYXgoY29ycmVjdF9kZiRkaWZmX3NkX3JhdGlvKWAuIFRoZSBJUVIgaXMgYHIgSVFSKGNvcnJlY3RfZGYkZGlmZl9zZF9yYXRpbylgLgoKIyMgRGlzdHJpYnV0aW9uIG9mIHVuY2VydGFpbnR5IHsudGFic2V0fQoKSW4gdGhlIGZvbGxvd2luZyB3ZSB3aWxsIHZpc3VhbGl6ZSB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIG91ciB2YXJpYWJlbHMuIFdlIHN0YXJ0IGJ5IGV4YW1pbmluZyB0aGUgZW1waXJpY2FsIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdW5jZXJ0YWludHkgZXN0aW1hdGVzICRcaGF0e1xzaWdtYX1fayQuCgojIyMgRnVsbCBkaXN0cmlidXRpb24KClRoZSBkaXN0cmlidXRpb24gYXBwZWFycyB0byBiZSBiaW1vZGFsLCB3aXRoIHBlYWtzIGNsb3NlIHRvIDAgYW5kIDAuMjoKCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTZ9CiMgRGlzdHJpYnV0aW9uIG9mIGVzdGltYXRlZCB1bmNlcnRhaW50eQpwMSA8LSBkZiAlPiUgCiAgZ2dwbG90KGFlcyh4PXVuY2VydGFpbnR5KSkgKwogIGdlb21faGlzdG9ncmFtKGNvbD0iZ3JleSIsIGJpbnMgPSA1MCwgYWxwaGE9LjUpICsKICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgZXN0aW1hdGVkIHVuY2VydGFpbnR5IikKcDEKYGBgCgojIyMgVW5jZXJ0YWludHkgYnkgcHJlZGljdGlvbgoKQnkgZ3JvdXBpbmcgdGhlIHVuY2VydGFpbnR5IGVzdGltYXRlcyBieSBgY29ycmVjdGAgKGkuZS4gaWYgdGhlIGxhYmVsIHdhcyBjb3JyZWN0bHkgcHJlZGljdGVkIG9yIG5vdCksIHdlIGNhbiBmaW5kIG91dCBob3cgdGhlIHByZWRpY3Rpb25zIGNvbnRyaWJ1dGUgdG8gdGhlIHVuY2VydGFpbnR5IGRpc3RyaWJ1dGlvbjoKCmBgYHtyfQojRGlzdHJpYnV0aW9uIG9mIGVzdGltYXRlZCB1bmNlcnRhaW50eSBieSBwcmVkaWN0aW9uCnAyIDwtIGRmICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW5jZXJ0YWludHksIGNvbD1hcy5mYWN0b3IoY29ycmVjdCkpKSArCiAgZ2VvbV9mcmVxcG9seShhbHBoYT0uNykgKwogIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBlc3RpbWF0ZWQgdW5jZXJ0YWludHkgYnkgY2xhc3NpZmljYXRpb24iKSArCiAgc2NhbGVfY29sb3JfZGlzY3JldGUobmFtZT0iUHJlZGljdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3M9YygiMCIsICIxIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiMDogSW5jb3JyZWN0IiwgIjE6IENvcnJlY3QiKSkKcDIKYGBgClRoZSBibHVlIGxpbmUgY29ycmVzcG9uZHMgdG8gdGhlIGNvcnJlY3QgcHJlZGljdGlvbnMsIHRoZSByZWQgbGluZSBjb3JyZXNwb25kcyB0byBpbmNvcnJlY3QgcHJlZGljdGlvbnMuIFdlIHNlZSB0aGF0IHRoZSBpbmNvcnJlY3QgcHJlZGljdGlvbnMgYXJlIGNlbnRlcmVkIGFyb3VuZCBhIGhpZ2hlciBhc3NvY2lhdGVkIHVuY2VydGFpbnR5LCB3aGVyZWFzIGZhciBtb3JlIG9mIHRoZSBjb3JyZWN0bHkgcHJlZGljdGVkIGNsYXNzZXMgYXJlIGNvbmNlbnRyYXRlZCBhcm91bmQgYSBsb3cgdW5jZXJ0YWludHkgdmFsdWUuIFRoZSBpbmNvcnJlY3QgY2xhc3NpZmljYXRpb25zIGdyZWF0bHkgY29udHJpYnV0ZSB0byB0aGUgYmltb2RhbGl0eSwgYnV0IGl0IGlzIGFsc28gcHJlc2VudCBpbiB0aGUgZGlzdHJpYnV0aW9uIG9mIHVuY2VydGFpbnR5IGZvciB0aGUgY29ycmVjdCBjbGFzc2lmaWNhdGlvbnMuIAoKIyMjIEtlcm5lbCBkZW5zaXR5IGVzdGltYXRlcwoKVGhlIGZvbGxvd2luZyBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0ZSBwbG90ICh1c2luZyBhIEdhdXNzaWFuIGtlcm5lbCkgZ2l2ZXMgdXMgYW4gaWRlYSBvZiBob3cgdGhlIGRpc3RyaWJ1dGlvbnMgY29tcGFyZSB0byBlYWNob3RoZXI6CgpgYGB7cn0KIyBLREUgYnkgY29ycmVjdCBwcmVkaWN0aW9uCnAzIDwtIGRmICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW5jZXJ0YWludHkpKSArCiAgZ2VvbV9kZW5zaXR5KGRhdGE9c3Vic2V0KGRmLCBjb3JyZWN0PT0wKSwgZmlsbD0icmVkIiwgYWxwaGE9SSguMikpICsKICBnZW9tX2RlbnNpdHkoZGF0YT1zdWJzZXQoZGYsIGNvcnJlY3Q9PTEpLCBmaWxsPSJ0dXJxdW9pc2UiLCBhbHBoYT1JKC4yKSkgKwogIGdndGl0bGUoIktlcm5lbCBkZW5zaXR5IGVzdGltYXRlcyBvZiB1bmNlcnRhaW50eSBkaXN0cmlidXRpb24iKQpwMwpgYGAKCiMjIyBCb3hwbG90cwoKVGhlIGJveHBsb3QgZ2l2ZXMgdXMgeWV0IGFub3RoZXIgd2F5IHRvIHZpc3VhbGl6ZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHVuY2VydGFpbnR5IGRpc3RyaWJ1dGlvbnMgYnkgcHJlZGljdGl2ZSBhYmlsaXR5OgoKYGBge3J9CiMgQm94cGxvdCBvZiB1bmNlcnRhaW50aWVzIGZvciBjb3JyZWN0IHZzLiBpbmNvcnJlY3QKcDQgPC0gZGYgJT4lIAogIGdncGxvdChhZXMoeD1hcy5mYWN0b3IoY29ycmVjdCksIHk9dW5jZXJ0YWludHkpKSArCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPWFzLmZhY3Rvcihjb3JyZWN0KSksIGFscGhhPS43KSArCiAgbGFicyh4PSJjb3JyZWN0IikgKwogIGdndGl0bGUoIkJveHBsb3Qgb2YgdW5jZXJ0YWludHkgZGlzdHJpYnV0aW9uIGJ5IGNvcnJlY3QvaW5jb3JyZWN0IikgKwogIHNjYWxlX2ZpbGxfZGlzY3JldGUobmFtZT0iUHJlZGljdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3M9YygiMCIsICIxIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiMDogSW5jb3JyZWN0IiwgIjE6IENvcnJlY3QiKSkKcDQKYGBgCgojIyBSZWxhdGlvbnNoaXAgdG8gb3RoZXIgdmFyaWFibGVzIHsudGFic2V0fQoKIyMjIFNvZnRtYXggb2YgcHJlZGljdGVkIGNsYXNzCgpXZSBtYXkgYWxzbyBiZSBpbnRlcmVzdGVkIGluIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBgdW5jZXJ0YWludHlgIGFuZCBvdGhlciB2YXJpYWJsZXMuIEZpcnN0LCB3ZSBwbG90IGB1bmNlcnRhaW50eWAgYWdhaW5zdCBgcHJvYjFgICh0aGUgcHJlZGljdGlvbidzIHNvZnRtYXggb3V0cHV0KToKCmBgYHtyfQojIFBsb3R0aW5nIHNvZnRtYXggb3V0cHV0IG9mIHByZWRpY3Rpb24gYWdhaW5zdCBlc3RpbWF0ZWQgdW5jZXJ0YWludHkKcDUgPC0gZGYgJT4lIAogIGdncGxvdChhZXMoeD1wcm9iMSwgeT11bmNlcnRhaW50eSkpICsKICBnZW9tX3BvaW50KGFscGhhPS4yKSArCiAgZ2d0aXRsZSgiVW5jZXJ0YWludHkgdnMuIHNvZnRtYXggb3V0cHV0IG9mIHByZWRpY3Rpb24gZm9yIGFsbCBvYnNlcnZhdGlvbnMiKSArCiAgbGFicyh4PSJzb2Z0bWF4IG91dHB1dCBvZiBwcmVkaWN0ZWQgY2xhc3MiKSArCiAgc2NhbGVfY29sb3JfZGlzdGlsbGVyKG5hbWU9IlNvZnRtYXggb3V0cHV0IiwKICAgICAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9ICJTcGVjdHJhbCIpCnA1CmBgYApUaGUgc29mdG1heCBvdXRwdXRzIGluIHRoZSBhYm92ZSBwbG90IGFyZSBjb2xvdXIgZ3JhZGVkLiBPdXRwdXRzIGNsb3NlIHRvIDEgYXJlIHJlZCwgb3V0cHV0cyBjbG9zZSB0byAwIGFyZSBibHVlLiBUaGUgcGxvdCBzaG93cyBhIGNsZWFyIHBhcmFib2xpYyBzaGFwZS4gCgojIyMgQnkgY2xhc3NpZmljYXRpb24KCmBgYHtyfQpwNiA8LSBkZiAlPiUgCiAgbXV0YXRlKGNvcnJlY3Q9YXMubG9naWNhbChjb3JyZWN0KSkgJT4lIAogIGdncGxvdChhZXMoeD1wcm9iMSwgeT11bmNlcnRhaW50eSkpICsKICBnZW9tX3BvaW50KGFlcyhmaWxsPWNvcnJlY3QsIGNvbD1jb3JyZWN0KSwgc2hhcGU9MjEsIGFscGhhPS41KSArCiAgZ2d0aXRsZSgiVW5jZXJ0YWludHkgdnMuIHNvZnRtYXggb3V0cHV0IG9mIHByZWRpY3Rpb24gZm9yIGFsbCBvYnNlcnZhdGlvbnMiKSArCiAgbGFicyh4PSJzb2Z0bWF4IG91dHB1dCBvZiBwcmVkaWN0ZWQgY2xhc3MiKQpwNgpgYGAKCgojIyMgUHJlZGljdGlvbnMgYW5kIHJ1bm5lcnMtdXAKCldlIGNhbiBvYnRhaW4gbW9yZSBpbmZvcm1hdGlvbiBieSBjb2xvdXJpbmcgdGhlIHBvaW50cyBieSB0aGUgdmFsdWUgb2YgdGhlIHJ1bm5lci11cCBwcmVkaWN0aW9uczoKCmBgYHtyfQojIFBsb3R0aW5nIHNvZnRtYXggb3V0cHV0IG9mIHByZWRpY3Rpb24gYWdhaW5zdCBlc3RpbWF0ZWQgdW5jZXJ0YWludHksIGNvbG91cmVkIGJ5IHNvZnRtYXggb3V0cHV0IG9mIHJ1bm5lci11cApwNyA8LSBkZiAlPiUgCiAgZ2dwbG90KGFlcyh4PXByb2IxLCB5PXVuY2VydGFpbnR5LCBjb2w9cHJvYjIpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0uNSkgKwogIGdndGl0bGUoIlVuY2VydGFpbnR5IHZzLiBzb2Z0bWF4IG91dHB1dCBvZiBwcmVkaWN0aW9uIGZvciBhbGwgb2JzZXJ2YXRpb25zIikgKwogIGxhYnMoeD0ic29mdG1heCBvdXRwdXQgb2YgcHJlZGljdGVkIGNsYXNzIikgKwogIHNjYWxlX2NvbG9yX2Rpc3RpbGxlcihuYW1lPSJSdW5uZXIgdXAiLAogICAgICAgICAgICAgICAgICAgICAgICBwYWxldHRlID0gIlNwZWN0cmFsIikKcDcKYGBgClRoaXMgcGxvdCBpcyBwYXJ0aWN1bGFybHkgaW50ZXJlc3RpbmcuIFRoZSBzb2Z0bWF4IG91dHB1dHMgb2YgdGhlIHJ1bm5lci11cCBjbGFzc2VzICRcaGF0e1xtdX1faiQgaW4gdGhlIGFib3ZlIHBsb3QgYXJlIGNvbG91ciBncmFkZWQuICRcaGF0e1xtdX1faiBcYXBwcm94IDAuNSQgYXJlIHJlZCwgJFxoYXR7XG11fV9qIFxhcHByb3ggMCQgYXJlIGJsdWUuIFdlIHNlZSBhIGNsZWFyIGNvbmNlbnRyYXRpb24gb2YgcmVkIHBvaW50cyBpbiB0aGUgYXJlYSB3aGVyZSB0aGUgcHJvYmFiaWxpdHkgb2YgdGhlIHByZWRpY3RpZWQgY2xhc3MgJFxoYXR7XG11fV9rIFxhcHByb3ggMC41JC4gSWYgdGhlIHNvZnRtYXggcHJlZGljdGlvbnMgb2YgYm90aCB0aGUgcHJlZGljdGVkIGNsYXNzIGFuZCB0aGUgcnVubmVyLXVwIGFyZSBjbG9zZSAwLjUsIHRoZW4gd2UgaGF2ZSBhIHNpdHVhdGlvbiBhbmFsb2dvdXMgdG8gbWF4aW11bSBlbnRyb3B5LiBUaGlzIHBvaW50cyBjb2luY2lkZSB3aXRoIHRoZSBsYXJnZXN0IGFwcHJveGltYXRlZCB1bmNlcnRhaW50eSB2YWx1ZXMuIAoKIyMjIFNvZnRtYXggb2YgcHJlZGljdGlvbiBieSBjbGFzc2lmaWNhdGlvbgoKSW4gdGhlIGZvbGxvd2luZyBwbG90IHdlIHNwbGl0IHRoZSBvYnNlcnZhdGlvbnMgYnkgaW5jb3JyZWN0L2NvcnJlY3QgcHJlZGljdGlvbnMsIGFuZCBwbG90IHRoZSB2YWx1ZXMgb2YgdW5jZXJ0YWludHkgYWdhaW5zdCB0aGUgc29mdG1heCBvdXRwdXQgb2YgdGhlIHByZWRpY3RlZCBjbGFzczoKCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01fQojIFBsb3R0aW5nIHVuY2VydGFpbnR5IHZzLiBzb2Z0bWF4IG91dHB1dCBvZiBwcmVkaWN0aW9uCnA4IDwtIGRmICU+JSAKICBnZ3Bsb3QoYWVzKHg9cHJvYjEsIHk9dW5jZXJ0YWludHkpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sPXByb2IyKSxhbHBoYT0wLjUpICsKICBnZW9tX2RlbnNpdHlfMmQoY29sPSJibGFjayIsIGFscGhhPS4zKSArCiAgZ2d0aXRsZSgiVW5jZXJ0YWludHkgdnMuIHNvZnRtYXggb3V0cHV0IG9mIHByZWRpY3Rpb24gYnkgaW5jb3JyZWN0L2NvcnJlY3QiKSArCiAgbGFicyh4PSJzb2Z0bWF4IG91dHB1dCBvZiBwcmVkaWN0aW9uIikgKwogIGZhY2V0X2dyaWQoLn5hcy5mYWN0b3IoY29ycmVjdCkpICsKICBzY2FsZV9jb2xvcl9kaXN0aWxsZXIobmFtZT0iUnVubmVyIHVwIiwKICAgICAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9ICJTcGVjdHJhbCIpCnA4CmBgYAoKVGhlIGJsYWNrIGNvbnRvdXIgbGluZXMgaW5kaWNhdGUgd2hlcmUgbW9zdCBvZiB0aGUgcG9pbnRzIGFyZSBjb25jZW50cmF0ZWQuIFRoZSBwbG90IG9uIHRoZSBsZWZ0IGlzIGZvciBpbmNvcnJlY3QgcHJlZGljdGlvbnMuIFRoZSByaWdodCBoYW5kIHBsb3QgcmVwcmVzZW50cyB0aGUgY29ycmVjdCBwcmVkaWN0aW9ucy4gRm9yIHRoZSBjb3JyZWN0IHByZWRpY3Rpb25zLCBpdCBzZWVtcyBhcyBpZiBmYXIgbW9yZSBvZiB0aGUgcG9pbnRzIGFyZSBjb25jZW50cmF0ZWQgYXJvdW5kIGhpZ2ggcHJlZGljdGVkIG91dHB1dC9sb3cgcnVubmVyLXVwIG91dHB1dC9sb3cgdW5jZXJ0YWludHkuIFRoaXMgaXMgbm90IHN1cnByaXNpbmcsIGNvbnNpZGVyaW5nIDc1JSBvZiB0aGUgY29ycmVjdCBwcmVkaWN0aW9ucyBoYXZlIGEgc29mdG1heCB2YWx1ZSBvZiBhcHByb3hpbWF0ZWx5IDAuNyBvciBhYm92ZS4gRm9yIHRoZSBpbmNvcnJlY3QgY2xhc3NpZmljYXRpb25zLCBtb3N0IG9mIHRoZSBwb2ludHMgYXJlIGNvbmNlbnRyYXRlZCBhcm91bmQgdGhlIGFyZWEgb2YgbWF4aW11bSBlbnRyb3B5LiBUaGlzIGluZGljYXRlcyB0aGF0IHRoZSBhcHByb3hpbWF0ZWQgdW5jZXJ0YWludHkgZXN0aW1hdGVzIGluZGVlZCBjb250YWluIHZhbHVhYmxlIGluZm9ybWF0aW9uIGluIHRoZSBpbmNvcnJlY3QgY2FzZXMuCgojIyMgUnVubmVyLXVwIHByZWRpY3Rpb25zCgpUaGUgZm9sbG93aW5nIHBsb3Qgc2hvd3MgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHVuY2VydGFpbnR5IGVzdGltYXRlcyBhbmQgdGhlIHNvZnRtYXggb3V0cHV0IG9mIHRoZSBydW5uZXItdXAgcHJlZGljdGlvbi4gVW5zdXJwcmlzaW5nbHksIG1vZGVsIHVuY2VydGFpbnR5IGluY3JlYXNlcyBhcyB0aGUgc29mdG1heCBvdXRwdXQgb2YgdGhlIHJ1bm5lci11cCBpbmNyZWFzZXMuIFdlIGhhdmUgcGxvdHRlZCBhIExPRVNTIGVzdGltYXRlIG9mIHRoZSBtZWFuIHVuY2VydGFpbnR5IGFzIGEgZnVuY3Rpb24gb2YgdGhlIHJ1bm5lci11cCBvdXRwdXQgdG8gbWFrZSB0aGlzIGNsZWFyZXI6CgpgYGB7cn0KIyBQbG90dGluZyBzb2Z0bWF4IG91dHB1dCBvZiBydW5uZXItdXAgYWdhaW5zdCBlc3RpbWF0ZWQgdW5jZXJ0YWludHkKcDEwIDwtIGRmICU+JSAKICBnZ3Bsb3QoYWVzKHg9cHJvYjIsIHk9dW5jZXJ0YWludHkpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0uMikgKwogIGdlb21fc21vb3RoKG1ldGhvZD0ibG9lc3MiKSArCiAgbGFicyh4PSJzb2Z0bWF4IG91dHB1dCBvZiBydW5uZXItdXAiKSArCiAgZ2d0aXRsZSgiVW5jZXJ0YWludHkgdnMuIHNvZnRtYXggb3V0cHV0IG9mIHJ1bm5lci11cCIpCnAxMApgYGAKCiMjIEltYWdlcyBhc3NvY2lhdGVkIHdpdGggaGlnaCB1bmNlcnRhaW50eSB7LnRhYnNldH0KClRoZSBmb2xsb3dpbmcgcGxvdHMgd2VyZSBnZW5lcmF0ZWQgdXNpbmcgUHl0aG9uIChjb2RlIGF2YWlsYWJsZSBvbiBbR2l0SHViXShsaW5rKSkuIE9uIHRoZSBsZWZ0IGhhbmQgc2lkZSB3ZSBzZWUgdGhlIHVubm9ybWFsaXplZCBpbWFnZSB3aXRoIHRoZSBjb3JyZXNwb25kaW5nIGdyb3VuZCB0cnV0aCBsYWJlbC4gVGhlIHBsb3QgaW4gdGhlIG1pZGRsZSBzaG93cyB0aGUgc29mdG1heCBvdXRwdXQgb2YgdGhlIHByZWRpY3RlZCBjbGFzcyBmb3IgZWFjaCBvZiB0aGUgJFQ9MTAwJCBzdG9jaGFzdGljIGZvcndhcmQgcGFzc2VzLiAkXG11X2skIGlzIGdpdmVuIGJ5IHRoZSBzb2xpZCByZWQgbGluZSwgJFxtdV9qJCBpcyBnaXZlbiBieSB0aGUgZGFzaGVkIGJyb3duIGxpbmUuIFRoZSBwbG90IHRpdGxlIHNob3dzIGJvdGggdGhlIHByZWRpY3RlZCBjbGFzcyBhbmQgdGhlIHJ1bm5lci11cCBjbGFzcy4gVG8gdGhlIHJpZ2h0IGlzIGEga2VybmVsIGRlbnNpdHkgZXN0aW1hdGUgKHVzaW5nIGEgR2F1c3NpYW4ga2VybmVsKSBvZiB0aGUgJFQ9MTAwJCBzb2Z0bWF4IG91dHB1dHMgZm9yIHRoZSBwcmVkaWN0ZWQgY2xhc3MuIFRoZSBwbG90cyBzaG93IHRoZSB0b3AgNSBtb3N0IHVuY2VydGFpbiBjbGFzc2lmaWNhdGlvbnMgaW4gdGhlIGVudGlyZSBkYXRhIHNldC4KCiMjIyBJbmNvcnJlY3QKIzxpbWcgc3JjPSJGaWd1cmVzLzg2MDEucGRmIj4KIzxpbWcgc3JjPSJGaWd1cmVzLzUzMDIucGRmIj4KIzxpbWcgc3JjPSJGaWd1cmVzLzQzNDAucGRmIj4KIzxpbWcgc3JjPSJGaWd1cmVzLzU1NzIucGRmIj4KIzxpbWcgc3JjPSJGaWd1cmVzLzEyNDgucGRmIj4KIVtdKEZpZ3VyZXMvODYwMS5qcGVnKQohW10oRmlndXJlcy81MzAyLmpwZWcpCiFbXShGaWd1cmVzLzQzNDAuanBlZykKIVtdKEZpZ3VyZXMvNTU3Mi5qcGVnKQohW10oRmlndXJlcy8xMjQ4LmpwZWcpCgojIyMgQ29ycmVjdAoKIzxpbWcgc3JjPSJGaWd1cmVzLzI1NzkucGRmIj4KIzxpbWcgc3JjPSJGaWd1cmVzLzk0NjgucGRmIj4KIzxpbWcgc3JjPSJGaWd1cmVzLzgxNjkucGRmIj4KIzxpbWcgc3JjPSJGaWd1cmVzLzc3NzgucGRmIj4KIzxpbWcgc3JjPSJGaWd1cmVzLzEwNzkucGRmIj4KIVtdKEZpZ3VyZXMvMjU3OS5qcGVnKQohW10oRmlndXJlcy85NDY4LmpwZWcpCiFbXShGaWd1cmVzLzgxNjkuanBlZykKIVtdKEZpZ3VyZXMvNzc3OC5qcGVnKQohW10oRmlndXJlcy8xMDc5LmpwZWcpCgoKRm9yIHNvbWUgdW5rbm93biByZWFzb24sIHRoZSBydW5uZXItdXAgcHJlZGljdGlvbiBmb3IgdGhlIGZpdmUgbW9zdCB1bmNlcnRhaW4gYnV0IGNvcnJlY3QgcHJlZGljdGlvbnMgYXJlIGFsbCAiYWlycGxhbmUiLiBPbmUgcG9zc2libGUgZXhwbGFuYXRpb24gaXMgdGhlIHByZXNlbmNlIG9mIGxhcmdlIGFyZWFzIG9mIGJsdWUgYW5kL29yIHdoaXRlLCB3aGljaCB3ZSBtaWdodCBhc3N1bWUgd291bGQgYmUgcHJlc2VudCBhcyBiYWNrZ3JvdW5kcyBpbiBpbWFnZXMgb2YgcGxhbmVzIGZseWluZyB0aHJvdWdoIHRoZSBza3kuCgojIyBVbmNlcnRhaW50eS1wcmVkaWN0aW9uIGNvcnJlbGF0aW9uCgpBcyBtZW50aW9uZWQgaW4gc2VjdGlvbiAyLjIsIHRoZSBjb25uZWN0aW9uIGVzdGFibGlzaGVkIGJldHdlZW4gZHJvcG91dCBuZXVyYWwgbmV0d29ya3MgYW5kIEdQcyBhcmUgbG9zdCB3aGVuIGFwcGxpZWQgdG8gY29udm9sdXRpb25hbCBuZXVyYWwgbmV0d29ya3MuIFBlcmZvcm1pbmcgYSBsb2dpc3RpYyByZWdyZXNzaW9uIGdpdmVzIHVzIGEgc2ltcGxlIHdheSBvZiB0ZXN0aW5nIGlmIHRoZSBhcHByb3hpbWF0ZWQgdW5jZXJ0YWludHkgaXMgYSBzaWduaWZpY2FudCBwcmVkaWN0b3Igb2YgdGhlIG1vZGVsJ3MgYWJpbGl0eSB0byBwcmVkaWN0IGNvcnJlY3RseToKCmBgYHtyfQojIEZpdHRpbmcgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0byBjaGVjayBzaWduaWZpY2FuY2Ugb2YgdW5jZXJ0YWludHkKbW9kZWxfc2QgPC0gZ2xtKGFzLmZhY3Rvcihjb3JyZWN0KX51bmNlcnRhaW50eSwgZGF0YT1kZiwgZmFtaWx5ID0gYmlub21pYWwobGluaz0ibG9naXQiKSkKc3VtbWFyeShtb2RlbF9zZCkKYGBgCgpUaGUgY29lZmZpY2llbnQgYXNzb2NpYXRlZCB3aXRoIGB1bmNlcnRhaW50eWAgaXMgaGlnaGx5IHNpZ25pZmljYW50LCBpbmRpY2F0aW5nIHRoYXQgdGhlIGVzdGltYXRlcyBnYXRoZXJlZCBmcm9tIHBlcmZvcm1pbmcgTUMgZHJvcG91dCBhcmUgaW5kZWVkIGEgdXNlZnVsIHF1YW50aWZpY2F0aW9uIG9mIHByZWRpY3RpdmUgdW5jZXJ0YWludHkuIEdpdmVuIGEgdW5pdCBpbmNyZWFzZSBpbiB1bmNlcnRhaW50eSwgd2UgZXhwZWN0IHRoZSBwcm9iYWJpbGl0eSBvZiBwcmVkaWN0aW5nIGNvcnJlY3RseSB0byBkZWNyZWFzZS4KCiMjIFJlZmVycmFsIGNyaXRlcmlhCgpUaGUgcXVlc3Rpb24gaXMgdGhlbjogSG93IGRvIHdlIGRldGVybWluZSBhIHJlYXNvbmFibGUgdW5jZXJ0YWludHkgdmFsdWUgZm9yIHJlZmVycmFsIHRvIGEgaHVtYW4gZXhwZXJ0PyBBcyB3ZSBzYXcgaW4gc2VjdGlvbiAzLjEuMiwgdGhlIG1lYW4gdW5jZXJ0YWludHkgdmFsdWVzIG9mIHRoZSBpbmNvcnJlY3QgcHJlZGljdGlvbnMgaGlnaGVyIHRoYW4gZm9yIHRoZSBjb3JyZWN0IHByZWRpY3Rpb25zIChgciBhZ2dfZGYkbWVhbl91bmNlcnRhaW50eVsxXWAgdnMuIGByIGFnZ19kZiRtZWFuX3VuY2VydGFpbnR5WzJdYCwgcmVzcGVjdGl2ZWx5KS4KCk5haXZlbHksIHdlIGNvdWxkIHNldCB0aGUgdGhyZXNob2xkIGZvciByZWZlcnJhbCB0byB0aGUgbWVhbiB1bmNlcnRhaW50eSBvZiB0aGUgaW5jb3JyZWN0bHkgY2xhc3NpZmllZCBpbWFnZXM6CgpgYGB7cn0KIyBDb3VudGluZyBudW1iZXIgb2YgY29ycmVjdC9pbmNvcnJlY3QgYnkgdW5jZXJ0YWludHkgPj0gLjE4IApyZWZlcnJhbCA8LSBkZiAlPiUgCiAgZmlsdGVyKHVuY2VydGFpbnR5Pj0uMTgpICU+JSAKICBjb3VudChjb3JyZWN0KQpyZWZlcnJhbApgYGAKClRoZSBwcm9ibGVtIGhlcmUgaXMgYXBwYXJlbnQ6IE1hbnkgb2YgdGhlIGNvcnJlY3RseSBjbGFzc2lmaWVkIGltYWdlcyBhcmUgYXNzb2NpYXRlZCB3aXRoIGEgcmVsYXRpdmVseSBoaWdoIGxldmVsIG9mIHVuY2VydGFpbnR5IChpbiBvdXIgY2FzZSwgdGhpcyBpcyBkdWUgdG8gdGhlIGJpbW9kYWxpdHkgb2YgdGhlIHVuY2VydGFpbnR5IGRpc3RyaWJ1dGlvbiBpbiBzZWN0aW9uIDMuMi4xKS4gV2Ugc3BlY3VsYXRlIHRoYXQgYWx0aG91Z2ggdW5jZXJ0YWludHkgc2VlbXMgdG8gY29udGFpbiB2YWx1ZWFibGUgaW5mb3JtYXRpb24sIHRoZSByZWxhdGl2ZSB1bmNlcnRhaW50aWVzIG9mIHRoZSBjb3JyZWN0L2luY29ycmVjdCBvYnNlcnZhdGlvbnMgKGluIHRoaXMgY2FzZSkgbWF5IGJlIHRvbyBzbWFsbCB0byBkaWZmZXJlbnRpYXRlIHdoaWNoIGltYWdlcyBzaG91bGQgYmUgcmVmZXJyZWQgdG8gYW4gZXhwZXJ0LiBXZSBtYXkgbmVlZCB0byBhbXBsaWZ5IHRoZSBxdWFudGlmaWNhdGlvbiBvZiB1bmNlcnRhaW50eSBpbiBzb21lIHdheS4gCgpPbmUgcG9zc2libGUgYXBwcm9hY2ggaXMgdG8gaW5jb3Jwb3JhdGUgaW5mb3JtYXRpb24gZnJvbSB0aGUgdmFsdWUgb2YgJFx0YXVfe2prfSQsIHdoaWNoIHdlIGludHJvZHVjZWQgaW4gc2VjdGlvbiAyLjUuIENvbnNpZGVyIHRoZSBmb2xsb3dpbmcgY2FzZXM6CgoqICRcdGF1X3tran0gXGFwcHJveCAxJCBtZWFucyB0aGF0IGBkaWZmYCBhbmQgYHVuY2VydGFpbnR5YCBhcmUgcmVsYXRpdmVseSBzaW1pbGFyLiBUaGlzIGhhcHBlbnMgaWYgYSkgdGhlIG1vZGVscyBoYXZlIGZhaWxlZCB0byByZWFjaCBhIGNvbnNlbnN1cyAoYGRpZmZgIGlzIHNtYWxsKSBidXQgbW9kZWwgdW5jZXJ0YWludHkgaXMgbG93LCBvciBiKSB0aGUgbW9kZWxzIGhhdmUgcmVhY2hlZCBhIGNvbnNlbnN1cyAoYGRpZmZgIGlzIGxhcmdlKSBidXQgbW9kZWwgdW5jZXJ0YWludHkgaXMgaGlnaC4gTGV0J3MgY2FsbCB0aGVzZSAqKnJlZmVycmFsIHByZWRpY3Rpb25zKiouCgoqICRcdGF1X3tran0gXHRvIDAkIG1lYW5zIHRoYXQgYHVuY2VydGFpbnR5YCBpcyBtdWNoIGxhcmdlciB0aGFuIGBkaWZmYC4gVGhlc2Ugc2hvdWxkIHJlcHJlc2VudCAqKnVuY2VydGFpbiBwcmVkaWN0aW9ucyoqLgoKKiAkXHRhdV97a2p9IFx0byBcaW5mdHkkIG1lYW5zIHRoYXQgYHVuY2VydGFpbnR5YCBpcyBtdWNoIHNtYWxsZXIgdGhhbiBgZGlmZmAuIFRoZXNlIHNob3VsZCByZXByZXNlbnQgKipub24tcmVmZXJyYWwgcHJlZGljdGlvbnMqKi4KCkFzIHdlIHNhdyBpbiB0YWJsZSAzLjEuMiwgdGhlIG1lYW4gJFx0YXVfe2prfSQgZm9yIHRoZSBpbmNvcnJlY3RseSBjbGFzc2lmaWVkIGltYWdlcyBpcyBgciBhZ2dfZGYkbWVhbl9yYXRpb1sxXWAgYW5kIGByIGFnZ19kZiRtZWFuX3JhdGlvWzJdYCBmb3IgdGhlIGNvcnJlY3RseSBjbGFzc2lmaWVkIGltYWdlcy4gU2V0dGluZyB0aGUgcmVmZXJyYWwgdGhyZXNob2xkIHRvIHRoZSBtZWFuICRcdGF1X3tqa30kIGZvciB0aGUgaW5jb3JyZWN0IGNsYXNzaWZpY2F0aW9ucyAoYHIgYWdnX2RmJG1lYW5fcmF0aW9bMV1gKSwgd2UgZ2V0OgoKYGBge3J9CiMgQ291bnRpbmcgbnVtYmVyIG9mIGNvcnJlY3QvaW5jb3JyZWN0IGJ5IHRhdSA8PSAyLjgKcmVmZXJyYWwgPC0gZGYgJT4lIAogIGZpbHRlcihkaWZmX3NkX3JhdGlvIDw9IDIuOCkgJT4lIAogIGNvdW50KGNvcnJlY3QpCnJlZmVycmFsCmBgYAoKV2UgcnVuIGludG8gdGhlIHNhbWUgcHJvYmxlbSBhcyB3aGVuIHdlIHVzZWQgdGhlIG1lYW4gdW5jZXJ0YWludHk6IE1hbnkgb2YgdGhlIGNvcnJlY3RseSBwcmVkaWN0ZWQgaW1hZ2VzIHdvdWxkIGJlIHJlZmVycmVkLCBpbiBmYWN0IG1vcmUgdGhhbiB3aGVuIHdlIHVzZWQgdGhlIHVuY2VydGFpbnR5IGVzdGltYXRlcy4gSXQgaXMgaW50ZXJlc3RpbmcgdG8gbm90ZSwgaG93ZXZlciwgdGhhdCB3ZSBnZXQgZmFyIG1vcmUgcmVmZXJyYWxzIG9mIGluY29ycmVjdGx5IGNsYXNzaWZpZWQgaW1hZ2VzLiBBZ2FpbiwgdGhpcyBtYXkgaW5kaWNhdGUgdGhhdCAkXHRhdV97amt9JCBpcyBhIHVzZWZ1bCBtZWFzdXJlIG9mIHVuY2VydGFpbnR5LgoKU2V0dGluZyB0aGUgcmVmZXJyYWwgcmF0ZSBvZiB0aGUgbWVhbiAkXHRhdV97amt9JCB0byAxIGdpdmVzIHVzIHRoZSBmb2xsb3dpbmc6CgpgYGB7cn0KIyBDb3VudGluZyBudW1iZXIgb2YgY29ycmVjdC9pbmNvcnJlY3QgYnkgdGF1IDw9IDEKcmVmZXJyYWwgPC0gZGYgJT4lIAogIGZpbHRlcihkaWZmX3NkX3JhdGlvIDw9IDEpICU+JSAKICBjb3VudChjb3JyZWN0KQpyZWZlcnJhbApgYGAKClRoaXMgaXMgYSBjbGVhciBpbXByb3ZlbWVudCBvdmVyIG91ciBmaXJzdCBhcHByb2FjaCwgaW4gdGVybXMgb2YgdGhlIGFtb3VudCBvZiByZWZlcnJlZCBpbWFnZXMuIDE2MSBmZXdlciBpbmNvcnJlY3QgaW1hZ2VzIGFyZSByZWZlcnJlZCBjb21wYXJlZCB0byA4ODkgZmV3ZXIgY29ycmVjdCBpbWFnZXMuIFRoaXMgY291bGQgaW5kaWNhdGUgdGhhdCAkXHRhdV97amt9JCBpcyBhIHVzZWZ1bCBxdWFudGlmaWNhdGlvbiBvZiB1bmNlcnRhaW50eS4KCioqQlJBSU5TVE9STUlORywgVVNJTkcgUlVOTkVSUy1VUDoqKiAKCmBgYHtyfQojIFdoYXQgaXMgYWNjdXJhY3kgaWYgd2UgdXNlIGRpZmZfc2RfcmF0aW8gYW5kIHJ1bm5lci11cCBhcyBwcmVkaWN0aW9uPwpyZWZlcnJhbCA8LSBkZiAlPiUgCiAgbXV0YXRlKGNvcnJlY3Q9cmVwbGFjZShjb3JyZWN0LCBkaWZmX3NkX3JhdGlvPD0xICYgY29ycmVjdD09MCAmIGNsYXNzMj09dHJ1dGgsIDEpKSAlPiUgCiAgY291bnQoY29ycmVjdCkKYGBgCgoKYGBge3J9CiMgV2hhdCBpcyBhY2N1cmFjeSBpZiB3ZSB1c2UgdW5jZXJ0YWludHkgYW5kIHJ1bm5lci11cCBhcyBwcmVkaWN0aW9uPwpyZWZlcnJhbCA8LSBkZiAlPiUgCiAgbXV0YXRlKGNvcnJlY3Q9cmVwbGFjZShjb3JyZWN0LCB1bmNlcnRhaW50eT49LjE4ICYgY29ycmVjdD09MCAmIGNsYXNzMj09dHJ1dGgsIDEpKSAlPiUgCiAgY291bnQoY29ycmVjdCkKYGBgCgoqKklERUE6KiogVXNpbmcgTERBIHRvIGZpbmQgYmVzdCB0YXUuCgpgYGB7cn0KZGYgJT4lIAogIGdncGxvdChhZXMoeD1sb2coZGlmZl9zZF9yYXRpbykpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKGZpbGw9YXMuZmFjdG9yKGNvcnJlY3QpKSkKYGBgCgpgYGB7cn0KbGlicmFyeShNQVNTKQpsZGFfZml0IDwtIGxkYShhcy5mYWN0b3IoY29ycmVjdCl+bG9nKGRpZmZfc2RfcmF0aW8pLCBkYXRhPWRmKQpsZGFfZml0CmBgYAoKCiMjIFVzZWZ1bG5lc3Mgb2YgcnVubmVyLXVwIHByZWRpY3Rpb25zCgpGb3IgdGhlIG1vc3QgdW5jZXJ0YWluIGluY29ycmVjdGx5IGNsYXNzaWZpZWQgaW1hZ2VzIHRoZSBydW5uZXItdXAgc3VnZ2VzdGlvbnMgYXJlIG5vbi1zZW5zaWNhbC4gU3RpbGwsIGlzIHRoZXJlIGFueSBpbmZvcm1hdGlvbiB0byBiZSBvYnRhaW5lZCBmcm9tIHRoZSBydW5uZXItdXAgcHJlZGljdGlvbnM/IFdoYXQgd291bGQgaGFwcGVuIHRvIG91ciBvdmVyYWxsIGFjY3VyYWN5IGlmIHdlIHVzZWQgdGhlIHJ1bm5lci11cCBwcmVkaWN0aW9ucyBmb3IgYWxsIGluY29ycmVjdCBjbGFzc2lmaWNhdGlvbnM/CgpgYGB7cn0KIyBDb3VudGluZyBjbGFzc2lmaWNhdGlvbiBhY2N1cmFjeSBpZiBydW5uZXItdXAgaXMgZXF1YWwgdG8gZ3JvdW5kIHRydXRoCmNsYXNzMl9kZiA8LSBkZiAlPiUKICBtdXRhdGUoY29ycmVjdD1yZXBsYWNlKGNvcnJlY3QsIGNvcnJlY3Q9PTAgJiBjbGFzczI9PXRydXRoLCAxKSkgJT4lIAogIGNvdW50KGNvcnJlY3QpCmNsYXNzMl9kZgpgYGAKCkFjY3VyYWN5IHdvdWxkIHJpc2UgdG8gYHIgY2xhc3MyX2RmJG5bMl0vKHN1bShjbGFzczJfZGYkbikpYC4gVGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGVyZSBtYXkgYmUgc29tZSB2YWx1YWJsZSBpbmZvcm1hdGlvbiB0byBiZSBnYXRoZXJlZCBmcm9tIHRoZSBydW5uZXItdXAgcHJlZGljdGlvbnMuIFNvIGhvdyBkbyB3ZSBkZXRlcm1pbmUgd2hpY2ggaW1hZ2VzIHRvIHRha2UgYSBjbG9zZXIgbG9vayBhdD8KCiMjIFVzaW5nIHRoZSBkaWZmZXJlbmNlLXVuY2VydGFpbnR5IHJhdGlvCgpgYGB7cn0KIyBGaXR0aW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgY2hlY2sgc2lnbmlmaWNhbmNlIG9mIGxvZy10cmFuc2Zvcm1lZCBkaWZmX3NkX3JhdGlvCm1vZGVsX3JhdGlvIDwtIGdsbShhcy5mYWN0b3IoY29ycmVjdCl+bG9nKGRpZmZfc2RfcmF0aW8pLCBkYXRhPWRmLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rPSJsb2dpdCIpKQpzdW1tYXJ5KG1vZGVsX3JhdGlvKQpgYGAKCmBgYHtyfQojIENoZWNraW5nIGZvciBpbnRlcmFjdGlvbnMgYmV0d2VlbiB1bmNlcnRhaW50eSBhbmQgbG9nLXRyYW5zZm9ybWVkIGRpZmZfc2RfcmF0aW8KbW9kZWxfaW50ZXIgPC0gZ2xtKGFzLmZhY3Rvcihjb3JyZWN0KX51bmNlcnRhaW50eSpsb2coZGlmZl9zZF9yYXRpbyksIGRhdGE9ZGYsIGZhbWlseSA9IGJpbm9taWFsKGxpbms9ImxvZ2l0IikpCnN1bW1hcnkobW9kZWxfaW50ZXIpCmBgYAoKYGBge3J9CiMgQ2hlY2tpbmcgbW9kZWxzCkFJQyhtb2RlbF9zZCwgbW9kZWxfcmF0aW8sIG1vZGVsX2ludGVyKQpgYGAKCiMgQ29tcGFyaXNvbiBvZiBhbGwgbW9kZWxzCgpXZSBnYXRoZXIgdGhlIGRhdGEgZnJvbSBhbGwgbW9kZWxzIGluIGEgc2luZ2xlIGRhdGFmcmFtZSBgZGZgOgoKYGBge3J9ClJPT1QgPC0gIn4vRG9jdW1lbnRzL01hc3Rlcm9wcGdhdmUvRGF0YS9SZXN1bHRhdGVyLyIKCiMgS2VybmVsIHNpemUgMywgZHJvcCByYXRlID0gLjIKZGYzMiA8LSBwcm9jZXNzKFJPT1QsICJsZW5ldC1tb2RlbDMyLmNzdiIsICJtb2RlbDMyIiwgMywgLjIpCiMgS2VybmVsIHNpemUgMywgZHJvcCByYXRlID0gLjUKZGYzNSA8LSBwcm9jZXNzKFJPT1QsICJsZW5ldC1tb2RlbDM1LmNzdiIsICJtb2RlbDM1IiwgMywgLjUpCiMgS2VybmVsIHNpemUgNSwgZHJvcCByYXRlID0gLjUKZGY1NSA8LSBwcm9jZXNzKFJPT1QsICJsZW5ldC1tb2RlbDU1LmNzdiIsICJtb2RlbDU1IiwgNSwgLjUpCiMgS2VybmVsIHNpemUgNSwgZHJvcCByYXRlID0gLjIKZGY1MiA8LSBwcm9jZXNzKFJPT1QsICJsZW5ldC1tb2RlbDUyLmNzdiIsICJtb2RlbDUyIiwgNSwgLjIpCgojIENvbWJpbmluZyBhbGwgZGF0YWZyYW1lcyBhbmQgYWRkaW5nIGtlcm5lbCBzaXplLCBkcm9wb3V0IHJhdGUKZGZfYWxsIDwtIGRmMzIgJT4lIAogIGJpbmRfcm93cyhkZjM1KSAlPiUgCiAgYmluZF9yb3dzKGRmNTIpICU+JSAKICBiaW5kX3Jvd3MoZGY1NSkgJT4lIAogIG11dGF0ZShjb3JyZWN0PWFzLmZhY3Rvcihhcy5sb2dpY2FsKGNvcnJlY3QpKSwKICAgICAgICAga2VybmVsPWFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIoa2VybmVsKSksCiAgICAgICAgIGRyb3BvdXQ9YXMuZmFjdG9yKGFzLmNoYXJhY3Rlcihkcm9wb3V0KSkpCmhlYWQoZGZfYWxsKQpzdW1tYXJ5KGRmX2FsbCkKYGBgCgpOb3RlIHRoYXQgd2UgaGF2ZSBhZGRlZCB0d28gbmV3IHZhcmlhYmxlcyB0byB0aGUgZGF0YSBzZXQ6IGBrZXJuZWxgIChmYWN0b3IsIGNvbnZvbHV0aW9uIHNpemUpIGFuZCBgZHJvcG91dGAgKGZhY3RvciwgZHJvcG91dCByYXRlKS4gSW4gdGhlIGZvbGxvd2luZyBjaHVuayB3ZSBnZW5lcmF0ZSBzb21lIHN1bW1hcnkgc3RhdGlzdGljczoKCmBgYHtyfQojIFN1bW1hcnkgc3RhdHMgZm9yIGFsbCBtb2RlbHMKdG90YWxfdW5jZXJ0YWludHkgPC0gZGZfYWxsICU+JSAKICBncm91cF9ieShtb2RlbCkgJT4lIAogIHN1bW1hcmlzZShhY2N1cmFjeT1zdW0oYXMubnVtZXJpYyhjb3JyZWN0KS0xKS8xMDAwMCwKICAgICAgICAgIG1lYW5fdW5jZXJ0YWludHk9bWVhbih1bmNlcnRhaW50eSksCiAgICAgICAgICBzZF91bmNlcnRhaW50eT1zZCh1bmNlcnRhaW50eSksIAogICAgICAgICAgbWVhbl9kaWZmPW1lYW4oZGlmZiksCiAgICAgICAgICBzZF9kaWZmPXNkKGRpZmYpLAogICAgICAgICAgbWVhbl9yYXRpbz1tZWFuKGRpZmZfc2RfcmF0aW8pLAogICAgICAgICAgc2RfcmF0aW89c2QoZGlmZl9zZF9yYXRpbykpICU+JSAKICBkcGx5cjo6YXJyYW5nZShtZWFuX3VuY2VydGFpbnR5KQp0b3RhbF91bmNlcnRhaW50eQpgYGAKCk92ZXJhbGw6CgoqIGBtb2RlbDUyYCBpcyB0aGUgbW9zdCBjZXJ0YWluLgoKKiBgbW9kZWwzNWAgaXMgdGhlIGxlYXN0IGNlcnRhaW4uCiAgICAKKiBJdCBzZWVtcyBhcyB0aG91Z2ggbW9kZWxzIHRyYWluZWQgd2l0aCBhIGhpZ2hlciBkcm9wb3V0IHJhdGUgYXJlIG1vcmUgYWNjdXJhdGUsIGJ1dCBsZXNzIGNlcnRhaW4uCgoqIFRoZSBtZWFuIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIHByZWRpY3RlZCBhbmQgcnVubmVyLXVwIGNsYXNzIGFyZSBjbG9zZSB0byAwLjYgZm9yIGFsbCBtb2RlbHMuCgoqIFRoZSBtb2RlbHMgd2l0aCB0aGUgbG93ZXN0IG1lYW4gdW5jZXJ0YWludHkgY29ycmVzcG9uZCB3aXRoIGxhcmdlIG1lYW4gdmFsdWVzIG9mICRcdGF1X3tqa30kLgoKKiBBbGwgbW9kZWxzIHNob3cgYW4gaW5jcmVhc2UgaW4gcHJlZGljdGlvbiBhY2N1cmFjeSBhcyBjb21wYXJlZCB0byB0aGVpciBiYXNlbGluZSBhY2N1cmFjeSBmcm9tIHRyYWluaW5nICh3aGVuIG5vIHN0b2NoYXN0aWMgZm9yd2FyZCBwYXNzZXMgd2VyZSBwZXJmb3JtZWQpOgogICAgKyBgbW9kZWw1MmA6IEFjY3VyYWN5IGhhcyBpbmNyZWFzZWQgYnkgYHIgKHRvdGFsX3VuY2VydGFpbnR5JGFjY3VyYWN5WzFdLTAuNzQ0MDYpYCBwZXJjZW50LgogICAgKyBgbW9kZWw1NWA6IEFjY3VyYWN5IGhhcyBpbmNyZWFzZWQgYnkgYHIgKHRvdGFsX3VuY2VydGFpbnR5JGFjY3VyYWN5WzNdLTAuNzYxMzcpYCBwZXJjZW50LgogICAgKyBgbW9kZWwzMmA6IEFjY3VyYWN5IGhhcyBpbmNyZWFzZWQgYnkgYHIgKHRvdGFsX3VuY2VydGFpbnR5JGFjY3VyYWN5WzJdLTAuNzQwOTApYCBwZXJjZW50LgogICAgKyBgbW9kZWwzNWA6IEFjY3VyYWN5IGhhcyBpbmNyZWFzZWQgYnkgYHIgKHRvdGFsX3VuY2VydGFpbnR5JGFjY3VyYWN5WzRdLTAuNzQyMTgpYCBwZXJjZW50LgoKYGBge3J9CiMgQWdncmVnYXRpbmcgc3VtbWFyeSBzdGF0aXN0aWNzIGJ5IG1vZGVsIGFuZCBjb3JyZWN0L2luY29ycmVjdAphZ2dfZGZfYWxsIDwtIGRmX2FsbCAlPiUgCiAgZ3JvdXBfYnkobW9kZWwsIGNvcnJlY3QpICU+JSAKICBzdW1tYXJpc2UocD1uKCkvMTAwMDAsCiAgICAgICAgICBtZWFuX3VuY2VydGFpbnR5PW1lYW4odW5jZXJ0YWludHkpLAogICAgICAgICAgc2RfdW5jZXJ0YWludHk9c2QodW5jZXJ0YWludHkpLCAKICAgICAgICAgIG1lYW5fZGlmZj1tZWFuKGRpZmYpLAogICAgICAgICAgc2RfZGlmZj1zZChkaWZmKSwKICAgICAgICAgIG1lYW5fcmF0aW89bWVhbihkaWZmX3NkX3JhdGlvKSwKICAgICAgICAgIHNkX3JhdGlvPXNkKGRpZmZfc2RfcmF0aW8pKQoKIyBTb3J0aW5nIGJ5IHVuY2VydGFpbnR5OiBtb3N0IGNlcnRhaW4gdG8gbGVhc3QgY2VydGFpbgpvcmRlcmVkX2RmIDwtIGFnZ19kZl9hbGwgJT4lIAogIGRwbHlyOjphcnJhbmdlKG1lYW5fdW5jZXJ0YWludHkpCm9yZGVyZWRfZGYKYGBgCgojIyBWaXN1YWxpc2F0aW9uc3sudGFic2V0fQoKIyMjIERpc3RyaWJ1dGlvbnMgb2YgdW5jZXJ0YWludHkKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0KIyBEaXN0cmlidXRpb25zIG9mIHVuY2VydGFpbnR5IGJ5IG1vZGVsIChsZWZ0KSBhbmQgYnkgbW9kZWwvY2xhc3NpZmljYXRpb24gKHJpZ2h0KQpwMTEgPC0gZGZfYWxsICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW5jZXJ0YWludHkpKSArIAogIGdlb21faGlzdG9ncmFtKGFscGhhPS41LCBmaWxsPSJibHVlIiwgYmlucz01MCkgKwogIGZhY2V0X2dyaWQoYXMuZmFjdG9yKG1vZGVsKX4uKSArCiAgZ2d0aXRsZSgiRGlzdHJpYnV0aW9ucyBvZiB1bmNlcnRhaW50eSIpCgpwMTIgPC0gZGZfYWxsICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW5jZXJ0YWludHksIGNvbD1jb3JyZWN0KSkgKwogIGdlb21fZnJlcXBvbHkoYWxwaGE9LjcpICsKICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgZXN0aW1hdGVkIHVuY2VydGFpbnR5IGJ5IGNsYXNzaWZpY2F0aW9uIikgKwogIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWU9IlByZWRpY3Rpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzPWMoIjAiLCAiMSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIjA6IEluY29ycmVjdCIsICIxOiBDb3JyZWN0IikpICsKICBmYWNldF9ncmlkKGFzLmZhY3Rvcihtb2RlbCl+LikKCmxheW91dDEgPC0gbWF0cml4KGMoMSwyKSwgbnJvdz0xKQptdWx0aXBsb3QocDExLCBwMTIsIGxheW91dD1sYXlvdXQxKQpgYGAKCiMjIyBLREVzIG9mIHVuY2VydGFpbnR5CmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9CiMgS0RFcwpwMTMgPC0gZGZfYWxsICU+JSAKICBnZ3Bsb3QoYWVzKHg9dW5jZXJ0YWludHkpKSArIAogIGdlb21fZGVuc2l0eShhZXMoZmlsbD1jb3JyZWN0KSwgYWxwaGE9LjcpICsKICBmYWNldF9ncmlkKGFzLmZhY3Rvcihtb2RlbCl+Y29ycmVjdCkgKwogIGdndGl0bGUoIktlcm5lbCBkZW5zaXR5IGVzdGltYXRpb25zIG9mIHVuY2VydGFpbnR5IGJ5IGNsYXNzaWZpY2F0aW9uIikKcDEzCmBgYAojIyMgQm94cGxvdHMKCmBgYHtyIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTEwfQojIEJveHBsb3RzIG9mIHVuY2VydGFpbnRpZXMKcDE0IDwtIGRmX2FsbCAlPiUgCiAgZ2dwbG90KGFlcyh4PWNvcnJlY3QsIHk9dW5jZXJ0YWludHkpKSArCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPWNvcnJlY3QpLCBhbHBoYT0uNykgKwogIGZhY2V0X2dyaWQoLn5hcy5mYWN0b3IobW9kZWwpKQoKIyBCb3hwbG90cyBvZiBsb2ctdHJhbnNmb3JtZWQgdGF1IGJ5IG1vZGVsIGFuZCBjbGFzc2lmaWNhdGlvbgpwMTUgPC0gZGZfYWxsICU+JSAKICBnZ3Bsb3QoYWVzKHg9Y29ycmVjdCwgeT1sb2coZGlmZl9zZF9yYXRpbykpKSArCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPWNvcnJlY3QpLCBhbHBoYT0uNykgKwogIGZhY2V0X2dyaWQoLn5hcy5mYWN0b3IobW9kZWwpKQoKbGF5b3V0MiA8LSBtYXRyaXgoYygyLDEpLCBieXJvdz1UUlVFKQptdWx0aXBsb3QocDE0LCBwMTUsIGxheW91dD1sYXlvdXQyKQoKYGBgCiMjIyBVbmNlcnRhaW50eSB2cy4gc29mdG1heCBvdXB1dApgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9N30KIyBQbG90dGluZyB1bmNlcnRhaW50eSBhZ2FpbnN0IHNvZnRtYXggb2YgcHJlZGljdGVkIGNsYXNzLCB3aXRoIGRlbnNpdHkgZXN0aW1hdGlvbiBjb250b3VycwpwMTYgPC0gZGZfYWxsICU+JSAKICBnZ3Bsb3QoYWVzKHg9cHJvYjEsIHk9dW5jZXJ0YWludHksIGNvbD1wcm9iMikpICsKICBnZW9tX3BvaW50KGFscGhhPS41KSArCiAgbGFicyh5PSJzb2Z0bWF4IG91dHB1dCBvZiBwcmVkaWN0ZWQgY2xhc3MiKSArCiAgc2NhbGVfY29sb3JfZGlzdGlsbGVyKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbGV0dGUgPSAiU3BlY3RyYWwiKSArCiAgZmFjZXRfZ3JpZCh+YXMuZmFjdG9yKG1vZGVsKX5jb3JyZWN0KQpwMTYKYGBgCiMjIyBVbmNlcnRhaW50eSB2cy4gc29mdG1heCBvdXB1dCB3aXRoIGNvbnRvdXIgY3VydmVzCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD03fQpwMTcgPC0gZGZfYWxsICU+JSAKICBnZ3Bsb3QoYWVzKHg9cHJvYjEsIHk9dW5jZXJ0YWludHksIGNvbD1wcm9iMikpICsKICBnZW9tX3BvaW50KGFscGhhPS41KSArCiAgZ2VvbV9kZW5zaXR5MmQoY29sPSJibGFjayIsIGFscGhhPS41KSArCiAgbGFicyh5PSJzb2Z0bWF4IG91dHB1dCBvZiBwcmVkaWN0ZWQgY2xhc3MiKSArCiAgc2NhbGVfY29sb3JfZGlzdGlsbGVyKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbGV0dGUgPSAiU3BlY3RyYWwiKSArCiAgZmFjZXRfZ3JpZCh+YXMuZmFjdG9yKG1vZGVsKX5jb3JyZWN0KQpwMTcKYGBgCgoKIyBSZWZlcmVuY2VzCgpbMV0gR2FsLCBZLiAmIEdoYWhyYW1hbmksIFouICpCYXllc2lhbiBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3JrcyB3aXRoIEJlcm5vdWxsaSBBcHByb3hpbWF0ZSBWYXJpYXRpb25hbCBJbmZlcmVuY2UqLiBhclhpdjogMTUwNi4wMjE1OCAoMjAxNSkuCgpbMl0gR2FsLCBZLiAmIEdoYWhyYW1hbmksIFouICpEcm9wb3V0IGFzIGEgQmF5ZXNpYW4gQXBwcm94aW1hdGlvbjogUmVwcmVzZW50aW5nIE1vZGVsIFVuY2VydGFpbnR5IGluIERlZXAgTGVhcm5pbmcqLiBhclhpdjogMTUwNi4wMjE0MiAoMjAxNSkuCgpbM10gR2FsLCBZLiAmIEdoYWhyYW1hbmksIFouICpEcm9wb3V0IGFzIGEgQmF5ZXNpYW4gQXBwcm94aW1hdGlvbjogQXBwZW5kaXgqLiBhclhpdjogMTUwNi4wMjE1NyAoMjAxNSkuCgpbNF0gTGVpYmlnLCBDLiAmIEFsbGtlbiwgVi4gZXQuIGFsLiAqTGV2ZXJhZ2luZyB1bmNlcnRhaW50eSBpbmZvcm1hdGlvbiBmcm9tIGRlZXAgbmV1cmFsIG5ldHdvcmtzIGZvciBkaXNlYXNlIGRldGVjdGlvbiouIFNjaWVudGlmaWMgUmVwb3J0cyB2b2x1bWUgNywgQXJ0aWNsZSBudW1iZXI6IDE3ODE2ICgyMDE3KS4KCls1XSBIaW50b24sIEcuICYgU3JpdmFzdGF2YSwgTi4gZXQuIGFsLiAqSW1wcm92aW5nIG5ldXJhbCBuZXR3b3JrcyBieSBwcmV2ZW50aW5nIGNvLWFkYXB0YXRpb24gb2YgZmVhdHVyZSBkZXRlY3RvcnMqLiBhclhpdjogMTIwNy4wNTgwICgyMDEyKS4KCls2XSBMZWliaWcsIEMuICYgQWxsa2VuLCBWLiBldC4gYWwuICpMZXZlcmFnaW5nIHVuY2VydGFpbnR5IGluZm9ybWF0aW9uIGZyb20gZGVlcCBuZXVyYWwgbmV0d29ya3MgZm9yIGRpc2Vhc2UgZGV0ZWN0aW9uOiBzdXBwbGVtZW50YXJ5IG1hdGVyaWFsKi4gU2NpZW50aWZpYyBSZXBvcnRzIHZvbHVtZSA3LCBBcnRpY2xlIG51bWJlcjogMTc4MTYgKDIwMTcpLg==